/* 3b2_cpu.c: AT&T 3B2 Model 400 CPU (WE32100) 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 <assert.h> | |
#include "3b2_cpu.h" | |
#include "rom_400_bin.h" | |
#define MAX_SUB_RETURN_SKIP 9 | |
/* RO memory. */ | |
uint32 *ROM = NULL; | |
/* Main memory. */ | |
uint32 *RAM = NULL; | |
/* Save environment for setjmp/longjmp */ | |
jmp_buf save_env; | |
volatile uint32 abort_context; | |
/* Pointer to the last decoded instruction */ | |
instr *cpu_instr; | |
/* The instruction to use if there is no history storage */ | |
instr inst; | |
/* Circular buffer of instructions */ | |
instr *INST = NULL; | |
uint32 cpu_hist_size = 0; | |
uint32 cpu_hist_p = 0; | |
t_bool cpu_in_wait = FALSE; | |
volatile size_t cpu_exception_stack_depth = 0; | |
volatile int32 stop_reason; | |
volatile uint32 abort_reason; | |
extern uint16 csr_data; | |
/* Register data */ | |
uint32 R[16]; | |
/* Other global CPU state */ | |
int8 cpu_dtype = -1; /* Default datatype for the current | |
instruction */ | |
int8 cpu_etype = -1; /* Currently set expanded datatype */ | |
t_bool cpu_nmi = FALSE; /* If set, there has been an NMI */ | |
uint8 cpu_ilen = 0; /* Length (in bytes) of instruction | |
currently being executed */ | |
t_bool cpu_ex_halt = FALSE; /* Flag to halt on exceptions / | |
traps */ | |
t_bool cpu_km = FALSE; /* If true, kernel mode has been forced | |
for memory access */ | |
BITFIELD psw_bits[] = { | |
BITFFMT(ET,2,%d), /* Exception Type */ | |
BIT(TM), /* Trace Mask */ | |
BITFFMT(ISC,4,%d), /* Internal State Code */ | |
BIT(I), /* Register Initial Context (I) */ | |
BIT(R), /* Register Initial Context (R) */ | |
BITFFMT(PM,2,%d), /* Previous Execution Level */ | |
BITFFMT(CM,2,%d), /* Current Execution Level */ | |
BITFFMT(IPL,4,%d), /* Interrupt Priority Level */ | |
BIT(TE), /* Trace Enable */ | |
BIT(C), /* Carry */ | |
BIT(V), /* Overflow */ | |
BIT(Z), /* Zero */ | |
BIT(N), /* Negative */ | |
BIT(OE), /* Enable Overflow Trap */ | |
BIT(CD), /* Cache Disable */ | |
BIT(QIE), /* Quick-Interrupt Enable */ | |
BIT(CFD), /* Cache Flush Disable */ | |
BITNCF(6), /* Unused */ | |
ENDBITS | |
}; | |
/* Registers. */ | |
REG cpu_reg[] = { | |
{ HRDATAD (PC, R[NUM_PC], 32, "Program Counter") }, | |
{ HRDATAD (R0, R[0], 32, "General purpose register 0") }, | |
{ HRDATAD (R1, R[1], 32, "General purpose register 1") }, | |
{ HRDATAD (R2, R[2], 32, "General purpose register 2") }, | |
{ HRDATAD (R3, R[3], 32, "General purpose register 3") }, | |
{ HRDATAD (R4, R[4], 32, "General purpose register 4") }, | |
{ HRDATAD (R5, R[5], 32, "General purpose register 5") }, | |
{ HRDATAD (R6, R[6], 32, "General purpose register 6") }, | |
{ HRDATAD (R7, R[7], 32, "General purpose register 7") }, | |
{ HRDATAD (R8, R[8], 32, "General purpose register 8") }, | |
{ HRDATAD (FP, R[NUM_FP], 32, "Frame Pointer") }, | |
{ HRDATAD (AP, R[NUM_AP], 32, "Argument Pointer") }, | |
{ HRDATADF (PSW, R[NUM_PSW], 32, "Processor Status Word", psw_bits) }, | |
{ HRDATAD (SP, R[NUM_SP], 32, "Stack Pointer") }, | |
{ HRDATAD (PCBP, R[NUM_PCBP], 32, "Process Control Block Pointer") }, | |
{ HRDATAD (ISP, R[NUM_ISP], 32, "Interrupt Stack Pointer") }, | |
{ NULL } | |
}; | |
static DEBTAB cpu_deb_tab[] = { | |
{ "READ", READ_MSG, "Memory read activity" }, | |
{ "WRITE", WRITE_MSG, "Memory write activity" }, | |
{ "DECODE", DECODE_MSG, "Instruction decode" }, | |
{ "EXECUTE", EXECUTE_MSG, "Instruction execute" }, | |
{ "INIT", INIT_MSG, "Initialization" }, | |
{ "IRQ", IRQ_MSG, "Interrupt Handling" }, | |
{ "IO", IO_D_MSG, "I/O Dispatch" }, | |
{ "TRACE", TRACE_MSG, "Call Trace" }, | |
{ NULL, 0 } | |
}; | |
UNIT cpu_unit = { UDATA (NULL, UNIT_FIX|UNIT_BINK|UNIT_IDLE, MAXMEMSIZE) }; | |
#define UNIT_V_EXHALT (UNIT_V_UF + 0) /* halt to console */ | |
#define UNIT_EXHALT (1u << UNIT_V_EXHALT) | |
MTAB cpu_mod[] = { | |
{ UNIT_MSIZE, (1u << 20), NULL, "1M", | |
&cpu_set_size, NULL, NULL, "Set Memory to 1M bytes" }, | |
{ UNIT_MSIZE, (1u << 21), NULL, "2M", | |
&cpu_set_size, NULL, NULL, "Set Memory to 2M bytes" }, | |
{ UNIT_MSIZE, (1u << 22), NULL, "4M", | |
&cpu_set_size, NULL, NULL, "Set Memory to 4M bytes" }, | |
{ MTAB_XTD|MTAB_VDV|MTAB_NMO|MTAB_SHP, 0, "HISTORY", "HISTORY", | |
&cpu_set_hist, &cpu_show_hist, NULL, "Displays instruction history" }, | |
{ MTAB_XTD|MTAB_VDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle }, | |
{ MTAB_XTD|MTAB_VDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL }, | |
{ UNIT_EXHALT, UNIT_EXHALT, "Halt on Exception", "EX_HALT", | |
NULL, NULL, NULL, "Enables Halt on exceptions and traps" }, | |
{ UNIT_EXHALT, 0, "No halt on exception", "NOEX_HALT", | |
NULL, NULL, NULL, "Disables Halt on exceptions and traps" }, | |
{ 0 } | |
}; | |
DEVICE cpu_dev = { | |
"CPU", /* Name */ | |
&cpu_unit, /* Units */ | |
cpu_reg, /* Registers */ | |
cpu_mod, /* Modifiers */ | |
1, /* Number of Units */ | |
16, /* Address radix */ | |
32, /* Address width */ | |
1, /* Addr increment */ | |
16, /* Data radix */ | |
8, /* Data width */ | |
&cpu_ex, /* Examine routine */ | |
&cpu_dep, /* Deposit routine */ | |
&cpu_reset, /* Reset routine */ | |
&cpu_boot, /* Boot routine */ | |
NULL, /* Attach routine */ | |
NULL, /* Detach routine */ | |
NULL, /* Context */ | |
DEV_DYNM|DEV_DEBUG, /* Flags */ | |
0, /* Debug control flags */ | |
cpu_deb_tab, /* Debug flag names */ | |
&cpu_set_size, /* Memory size change */ | |
NULL /* Logical names */ | |
}; | |
#define HWORD_OP_COUNT 11 | |
mnemonic hword_ops[HWORD_OP_COUNT] = { | |
{0x3009, 0, OP_NONE, NA, "MVERNO", -1, -1, -1, -1}, | |
{0x300d, 0, OP_NONE, NA, "ENBVJMP", -1, -1, -1, -1}, | |
{0x3013, 0, OP_NONE, NA, "DISVJMP", -1, -1, -1, -1}, | |
{0x3019, 0, OP_NONE, NA, "MOVBLW", -1, -1, -1, -1}, | |
{0x301f, 0, OP_NONE, NA, "STREND", -1, -1, -1, -1}, | |
{0x302f, 1, OP_DESC, WD, "INTACK", -1, -1, -1, -1}, | |
{0x3035, 0, OP_NONE, NA, "STRCPY", -1, -1, -1, -1}, | |
{0x3045, 0, OP_NONE, NA, "RETG", -1, -1, -1, -1}, | |
{0x3061, 0, OP_NONE, NA, "GATE", -1, -1, -1, -1}, | |
{0x30ac, 0, OP_NONE, NA, "CALLPS", -1, -1, -1, -1}, | |
{0x30c8, 0, OP_NONE, NA, "RETPS", -1, -1, -1, -1} | |
}; | |
/* Lookup table of operand types. */ | |
mnemonic ops[256] = { | |
{0x00, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x01, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x02, 2, OP_COPR, WD, "SPOPRD", -1, -1, -1, -1}, | |
{0x03, 3, OP_COPR, WD, "SPOPD2", -1, -1, -1, -1}, | |
{0x04, 2, OP_DESC, WD, "MOVAW", 0, -1, -1, 1}, | |
{0x05, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x06, 2, OP_COPR, WD, "SPOPRT", -1, -1, -1, -1}, | |
{0x07, 3, OP_COPR, WD, "SPOPT2", -1, -1, -1, -1}, | |
{0x08, 0, OP_NONE, NA, "RET", -1, -1, -1, -1}, | |
{0x09, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x0a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x0b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x0c, 2, OP_DESC, WD, "MOVTRW", 0, -1, -1, 1}, | |
{0x0d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x0e, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x0f, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x10, 1, OP_DESC, WD, "SAVE", 0, -1, -1, -1}, | |
{0x11, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x12, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x13, 2, OP_COPR, WD, "SPOPWD", -1, -1, -1, -1}, | |
{0x14, 1, OP_BYTE, NA, "EXTOP", -1, -1, -1, -1}, | |
{0x15, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x16, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x17, 2, OP_COPR, WD, "SPOPWT", -1, -1, -1, -1}, | |
{0x18, 1, OP_DESC, WD, "RESTORE", 0, -1, -1, -1}, | |
{0x19, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x1a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x1b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x1c, 1, OP_DESC, WD, "SWAPWI", -1, -1, -1, 0}, /* 3-122 252 */ | |
{0x1d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x1e, 1, OP_DESC, HW, "SWAPHI", -1, -1, -1, 0}, /* 3-122 252 */ | |
{0x1f, 1, OP_DESC, BT, "SWAPBI", -1, -1, -1, 0}, /* 3-122 252 */ | |
{0x20, 1, OP_DESC, WD, "POPW", -1, -1, -1, 0}, | |
{0x21, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x22, 2, OP_COPR, WD, "SPOPRS", -1, -1, -1, -1}, | |
{0x23, 3, OP_COPR, WD, "SPOPS2", -1, -1, -1, -1}, | |
{0x24, 1, OP_DESC, NA, "JMP", -1, -1, -1, 0}, | |
{0x25, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x26, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x27, 0, OP_NONE, NA, "CFLUSH", -1, -1, -1, -1}, | |
{0x28, 1, OP_DESC, WD, "TSTW", 0, -1, -1, -1}, | |
{0x29, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x2a, 1, OP_DESC, HW, "TSTH", 0, -1, -1, -1}, | |
{0x2b, 1, OP_DESC, BT, "TSTB", 0, -1, -1, -1}, | |
{0x2c, 2, OP_DESC, WD, "CALL", 0, -1, -1, 1}, | |
{0x2d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x2e, 0, OP_NONE, NA, "BPT", -1, -1, -1, -1}, | |
{0x2f, 0, OP_NONE, NA, "WAIT", -1, -1, -1, -1}, | |
{0x30, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, /* Two-byte instructions */ | |
{0x31, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x32, 1, OP_COPR, WD, "SPOP", -1, -1, -1, -1}, | |
{0x33, 2, OP_COPR, WD, "SPOPWS", -1, -1, -1, -1}, | |
{0x34, 1, OP_DESC, WD, "JSB", -1, -1, -1, 0}, | |
{0x35, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x36, 1, OP_HALF, NA, "BSBH", -1, -1, -1, 0}, | |
{0x37, 1, OP_BYTE, NA, "BSBB", -1, -1, -1, 0}, | |
{0x38, 2, OP_DESC, WD, "BITW", 0, 1, -1, -1}, | |
{0x39, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x3a, 2, OP_DESC, HW, "BITH", 0, 1, -1, -1}, | |
{0x3b, 2, OP_DESC, BT, "BITB", 0, 1, -1, -1}, | |
{0x3c, 2, OP_DESC, WD, "CMPW", 0, 1, -1, -1}, | |
{0x3d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x3e, 2, OP_DESC, HW, "CMPH", 0, 1, -1, -1}, | |
{0x3f, 2, OP_DESC, BT, "CMPB", 0, 1, -1, -1}, | |
{0x40, 0, OP_NONE, NA, "RGEQ", -1, -1, -1, -1}, | |
{0x41, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x42, 1, OP_HALF, NA, "BGEH", -1, -1, -1, 0}, | |
{0x43, 1, OP_BYTE, NA, "BGEB", -1, -1, -1, 0}, | |
{0x44, 0, OP_NONE, NA, "RGTR", -1, -1, -1, -1}, | |
{0x45, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x46, 1, OP_HALF, NA, "BGH", -1, -1, -1, 0}, | |
{0x47, 1, OP_BYTE, NA, "BGB", -1, -1, -1, 0}, | |
{0x48, 0, OP_NONE, NA, "RLSS", -1, -1, -1, 0}, | |
{0x49, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x4a, 1, OP_HALF, NA, "BLH", -1, -1, -1, 0}, | |
{0x4b, 1, OP_BYTE, NA, "BLB", -1, -1, -1, 0}, | |
{0x4c, 0, OP_NONE, NA, "RLEQ", -1, -1, -1, -1}, | |
{0x4d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x4e, 1, OP_HALF, NA, "BLEH", -1, -1, -1, 0}, | |
{0x4f, 1, OP_BYTE, NA, "BLEB", -1, -1, -1, 0}, | |
{0x50, 0, OP_NONE, NA, "BGEQU", -1, -1, -1, 0}, /* aka BCC */ | |
{0x51, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x52, 1, OP_HALF, NA, "BGEUH", -1, -1, -1, 0}, /* aka BCCH */ | |
{0x53, 1, OP_BYTE, NA, "BGEUB", -1, -1, -1, 0}, /* aka BCCB */ | |
{0x54, 0, OP_NONE, NA, "RGTRU", -1, -1, -1, -1}, | |
{0x55, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x56, 1, OP_HALF, NA, "BGUH", -1, -1, -1, 0}, | |
{0x57, 1, OP_BYTE, NA, "BGUB", -1, -1, -1, 0}, | |
{0x58, 0, OP_NONE, NA, "RLSSU", -1, -1, -1, 0}, /* aka BCS */ | |
{0x59, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x5a, 1, OP_HALF, NA, "BLUH", -1, -1, -1, 0}, /* aka BCSH */ | |
{0x5b, 1, OP_BYTE, NA, "BLUB", -1, -1, -1, 0}, /* aka BCSB */ | |
{0x5c, 0, OP_NONE, NA, "RLEQU", -1, -1, -1, -1}, | |
{0x5d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x5e, 1, OP_HALF, NA, "BLEUH", -1, -1, -1, 0}, | |
{0x5f, 1, OP_BYTE, NA, "BLEUB", -1, -1, -1, 0}, | |
{0x60, 0, OP_NONE, NA, "RVC", -1, -1, -1, -1}, | |
{0x61, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x62, 1, OP_HALF, NA, "BVCH", -1, -1, -1, 0}, | |
{0x63, 1, OP_BYTE, NA, "BVCB", -1, -1, -1, 0}, | |
{0x64, 0, OP_NONE, NA, "RNEQU", -1, -1, -1, -1}, | |
{0x65, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x66, 1, OP_HALF, NA, "BNEH", -1, -1, -1, 0}, /* duplicate of 76 */ | |
{0x67, 1, OP_BYTE, NA, "BNEB", -1, -1, -1, 0}, /* duplicate of 77*/ | |
{0x68, 0, OP_NONE, NA, "RVS", -1, -1, -1, -1}, | |
{0x69, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x6a, 1, OP_HALF, NA, "BVSH", -1, -1, -1, 0}, | |
{0x6b, 1, OP_BYTE, NA, "BVSB", -1, -1, -1, 0}, | |
{0x6c, 0, OP_NONE, NA, "REQLU", -1, -1, -1, -1}, | |
{0x6d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x6e, 1, OP_HALF, NA, "BEH", -1, -1, -1, 0}, /* duplicate of 7e */ | |
{0x6f, 1, OP_BYTE, NA, "BEB", -1, -1, -1, 0}, /* duplicate of 7f */ | |
{0x70, 0, OP_NONE, NA, "NOP", -1, -1, -1, -1}, | |
{0x71, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x72, 0, OP_NONE, NA, "NOP3", -1, -1, -1, -1}, | |
{0x73, 0, OP_NONE, NA, "NOP2", -1, -1, -1, -1}, | |
{0x74, 0, OP_NONE, NA, "RNEQ", -1, -1, -1, -1}, | |
{0x75, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x76, 1, OP_HALF, NA, "BNEH", -1, -1, -1, 0}, /* duplicate of 66 */ | |
{0x77, 1, OP_BYTE, NA, "BNEB", -1, -1, -1, 0}, /* duplicate of 67 */ | |
{0x78, 0, OP_NONE, NA, "RSB", -1, -1, -1, -1}, | |
{0x79, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x7a, 1, OP_HALF, NA, "BRH", -1, -1, -1, 0}, | |
{0x7b, 1, OP_BYTE, NA, "BRB", -1, -1, -1, 0}, | |
{0x7c, 0, OP_NONE, NA, "REQL", -1, -1, -1, -1}, | |
{0x7d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x7e, 1, OP_HALF, NA, "BEH", -1, -1, -1, 0}, /* duplicate of 6e */ | |
{0x7f, 1, OP_BYTE, NA, "BEB", -1, -1, -1, 0}, /* duplicate of 6f */ | |
{0x80, 1, OP_DESC, WD, "CLRW", -1, -1, -1, 0}, | |
{0x81, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x82, 1, OP_DESC, HW, "CLRH", -1, -1, -1, 0}, | |
{0x83, 1, OP_DESC, BT, "CLRB", -1, -1, -1, 0}, | |
{0x84, 2, OP_DESC, WD, "MOVW", 0, -1, -1, 1}, | |
{0x85, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x86, 2, OP_DESC, HW, "MOVH", 0, -1, -1, 1}, | |
{0x87, 2, OP_DESC, BT, "MOVB", 0, -1, -1, 1}, | |
{0x88, 2, OP_DESC, WD, "MCOMW", 0, -1, -1, 1}, | |
{0x89, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x8a, 2, OP_DESC, HW, "MCOMH", 0, -1, -1, 1}, | |
{0x8b, 2, OP_DESC, BT, "MCOMB", 0, -1, -1, 1}, | |
{0x8c, 2, OP_DESC, WD, "MNEGW", 0, -1, -1, 1}, | |
{0x8d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x8e, 2, OP_DESC, HW, "MNEGH", 0, -1, -1, 1}, | |
{0x8f, 2, OP_DESC, BT, "MNEGB", 0, -1, -1, 1}, | |
{0x90, 1, OP_DESC, WD, "INCW", -1, -1, -1, 0}, | |
{0x91, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x92, 1, OP_DESC, HW, "INCH", -1, -1, -1, 0}, | |
{0x93, 1, OP_DESC, BT, "INCB", -1, -1, -1, 0}, | |
{0x94, 1, OP_DESC, WD, "DECW", -1, -1, -1, 0}, | |
{0x95, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x96, 1, OP_DESC, HW, "DECH", -1, -1, -1, 0}, | |
{0x97, 1, OP_DESC, BT, "DECB", -1, -1, -1, 0}, | |
{0x98, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x99, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x9a, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x9b, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x9c, 2, OP_DESC, WD, "ADDW2", 0, -1, -1, 1}, | |
{0x9d, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0x9e, 2, OP_DESC, HW, "ADDH2", 0, -1, -1, 1}, | |
{0x9f, 2, OP_DESC, BT, "ADDB2", 0, -1, -1, 1}, | |
{0xa0, 1, OP_DESC, WD, "PUSHW", 0, -1, -1, -1}, | |
{0xa1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xa2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xa3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xa4, 2, OP_DESC, WD, "MODW2", 0, -1, -1, 1}, | |
{0xa5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xa6, 2, OP_DESC, HW, "MODH2", 0, -1, -1, 1}, | |
{0xa7, 2, OP_DESC, BT, "MODB2", 0, -1, -1, 1}, | |
{0xa8, 2, OP_DESC, WD, "MULW2", 0, -1, -1, 1}, | |
{0xa9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xaa, 2, OP_DESC, HW, "MULH2", 0, -1, -1, 1}, | |
{0xab, 2, OP_DESC, BT, "MULB2", 0, -1, -1, 1}, | |
{0xac, 2, OP_DESC, WD, "DIVW2", 0, -1, -1, 1}, | |
{0xad, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xae, 2, OP_DESC, HW, "DIVH2", 0, -1, -1, 1}, | |
{0xaf, 2, OP_DESC, BT, "DIVB2", 0, -1, -1, 1}, | |
{0xb0, 2, OP_DESC, WD, "ORW2", 0, -1, -1, 1}, | |
{0xb1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xb2, 2, OP_DESC, HW, "ORH2", 0, -1, -1, 1}, | |
{0xb3, 2, OP_DESC, BT, "ORB2", 0, -1, -1, 1}, | |
{0xb4, 2, OP_DESC, WD, "XORW2", 0, -1, -1, 1}, | |
{0xb5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xb6, 2, OP_DESC, HW, "XORH2", 0, -1, -1, 1}, | |
{0xb7, 2, OP_DESC, BT, "XORB2", 0, -1, -1, 1}, | |
{0xb8, 2, OP_DESC, WD, "ANDW2", 0, -1, -1, 1}, | |
{0xb9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xba, 2, OP_DESC, HW, "ANDH2", 0, -1, -1, 1}, | |
{0xbb, 2, OP_DESC, BT, "ANDB2", 0, -1, -1, 1}, | |
{0xbc, 2, OP_DESC, WD, "SUBW2", 0, -1, -1, 1}, | |
{0xbd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xbe, 2, OP_DESC, HW, "SUBH2", 0, -1, -1, 1}, | |
{0xbf, 2, OP_DESC, BT, "SUBB2", 0, -1, -1, 1}, | |
{0xc0, 3, OP_DESC, WD, "ALSW3", 0, 1, -1, 2}, | |
{0xc1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xc2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xc3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xc4, 3, OP_DESC, WD, "ARSW3", 0, 1, -1, 2}, | |
{0xc5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xc6, 3, OP_DESC, HW, "ARSH3", 0, 1, -1, 2}, | |
{0xc7, 3, OP_DESC, BT, "ARSB3", 0, 1, -1, 2}, | |
{0xc8, 4, OP_DESC, WD, "INSFW", 0, 1, 2, 3}, | |
{0xc9, -1, OP_DESC, NA, "???", -1, -1, -1, -1}, | |
{0xca, 4, OP_DESC, HW, "INSFH", 0, 1, 2, 3}, | |
{0xcb, 4, OP_DESC, BT, "INSFB", 0, 1, 2, 3}, | |
{0xcc, 4, OP_DESC, WD, "EXTFW", 0, 1, 2, 3}, | |
{0xcd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xce, 4, OP_DESC, HW, "EXTFH", 0, 1, 2, 3}, | |
{0xcf, 4, OP_DESC, BT, "EXTFB", 0, 1, 2, 3}, | |
{0xd0, 3, OP_DESC, WD, "LLSW3", 0, 1, -1, 2}, | |
{0xd1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xd2, 3, OP_DESC, HW, "LLSH3", 0, 1, -1, 2}, | |
{0xd3, 3, OP_DESC, BT, "LLSB3", 0, 1, -1, 2}, | |
{0xd4, 3, OP_DESC, WD, "LRSW3", 0, 1, -1, 2}, | |
{0xd5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xd6, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xd7, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xd8, 3, OP_DESC, WD, "ROTW", 0, 1, -1, 2}, /* 3-108 238 */ | |
{0xd9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xda, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xdb, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xdc, 3, OP_DESC, WD, "ADDW3", 0, 1, -1, 2}, | |
{0xdd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xde, 3, OP_DESC, HW, "ADDH3", 0, 1, -1, 2}, | |
{0xdf, 3, OP_DESC, BT, "ADDB3", 0, 1, -1, 2}, | |
{0xe0, 1, OP_DESC, WD, "PUSHAW", 0, -1, -1, -1}, | |
{0xe1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xe2, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xe3, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xe4, 3, OP_DESC, WD, "MODW3", 0, 1, -1, 2}, | |
{0xe5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xe6, 3, OP_DESC, HW, "MODH3", 0, 1, -1, 2}, | |
{0xe7, 3, OP_DESC, BT, "MODB3", 0, 1, -1, 2}, | |
{0xe8, 3, OP_DESC, WD, "MULW3", 0, 1, -1, 2}, | |
{0xe9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xea, 3, OP_DESC, HW, "MULH3", 0, 1, -1, 2}, | |
{0xeb, 3, OP_DESC, BT, "MULB3", 0, 1, -1, 2}, | |
{0xec, 3, OP_DESC, WD, "DIVW3", 0, 1, -1, 2}, | |
{0xed, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xee, 3, OP_DESC, HW, "DIVH3", 0, 1, -1, 2}, | |
{0xef, 3, OP_DESC, BT, "DIVB3", 0, 1, -1, 2}, | |
{0xf0, 3, OP_DESC, WD, "ORW3", 0, 1, -1, 2}, | |
{0xf1, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xf2, 3, OP_DESC, HW, "ORH3", 0, 1, -1, 2}, | |
{0xf3, 3, OP_DESC, BT, "ORB3", 0, 1, -1, 2}, | |
{0xf4, 3, OP_DESC, WD, "XORW3", 0, 1, -1, 2}, | |
{0xf5, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xf6, 3, OP_DESC, HW, "XORH3", 0, 1, -1, 2}, | |
{0xf7, 3, OP_DESC, BT, "XORB3", 0, 1, -1, 2}, | |
{0xf8, 3, OP_DESC, WD, "ANDW3", 0, 1, -1, 2}, | |
{0xf9, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xfa, 3, OP_DESC, HW, "ANDH3", 0, 1, -1, 2}, | |
{0xfb, 3, OP_DESC, BT, "ANDB3", 0, 1, -1, 2}, | |
{0xfc, 3, OP_DESC, WD, "SUBW3", 0, 1, -1, 2}, | |
{0xfd, -1, OP_NONE, NA, "???", -1, -1, -1, -1}, | |
{0xfe, 3, OP_DESC, HW, "SUBH3", 0, 1, -1, 2}, | |
{0xff, 3, OP_DESC, BT, "SUBB3", 0, 1, -1, 2} | |
}; | |
/* from MAME (src/devices/cpu/m68000/m68kcpu.c) */ | |
const uint8 shift_8_table[65] = | |
{ | |
0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff, 0xff, 0xff, 0xff, | |
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | |
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | |
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | |
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, | |
0xff, 0xff, 0xff, 0xff, 0xff | |
}; | |
const uint16 shift_16_table[65] = | |
{ | |
0x0000, 0x8000, 0xc000, 0xe000, 0xf000, 0xf800, 0xfc00, 0xfe00, 0xff00, | |
0xff80, 0xffc0, 0xffe0, 0xfff0, 0xfff8, 0xfffc, 0xfffe, 0xffff, 0xffff, | |
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, | |
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, | |
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, | |
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, | |
0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, | |
0xffff, 0xffff | |
}; | |
const uint32 shift_32_table[65] = | |
{ | |
0x00000000, 0x80000000, 0xc0000000, 0xe0000000, 0xf0000000, 0xf8000000, | |
0xfc000000, 0xfe000000, 0xff000000, 0xff800000, 0xffc00000, 0xffe00000, | |
0xfff00000, 0xfff80000, 0xfffc0000, 0xfffe0000, 0xffff0000, 0xffff8000, | |
0xffffc000, 0xffffe000, 0xfffff000, 0xfffff800, 0xfffffc00, 0xfffffe00, | |
0xffffff00, 0xffffff80, 0xffffffc0, 0xffffffe0, 0xfffffff0, 0xfffffff8, | |
0xfffffffc, 0xfffffffe, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, | |
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, | |
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, | |
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, | |
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, | |
0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff | |
}; | |
void cpu_load_rom() | |
{ | |
uint32 i, index, sc, mask, val; | |
if (ROM == NULL) { | |
return; | |
} | |
for (i = 0; i < BOOT_CODE_SIZE; i++) { | |
val = BOOT_CODE_ARRAY[i]; | |
sc = (~(i & 3) << 3) & 0x1f; | |
mask = 0xffu << sc; | |
index = i >> 2; | |
ROM[index] = (ROM[index] & ~mask) | (val << sc); | |
} | |
} | |
t_stat cpu_boot(int32 unit_num, DEVICE *dptr) | |
{ | |
/* | |
* page 2-52 (pdf page 85) | |
* | |
* 1. Change to physical address mode | |
* 2. Fetch the word at physical address 0x80 and store it in | |
* the PCBP register. | |
* 3. Fetch the word at the PCB address and store it in the | |
* PSW. | |
* 4. Fetch the word at PCB address + 4 bytes and store it | |
* in the PC. | |
* 5. Fetch the word at PCB address + 8 bytes and store it | |
* in the SP. | |
* 6. Fetch the word at PCB address + 12 bytes and store it | |
* in the PCB, if bit I in PSW is set. | |
*/ | |
mmu_disable(); | |
R[NUM_PCBP] = pread_w(0x80); | |
R[NUM_PSW] = pread_w(R[NUM_PCBP]); | |
R[NUM_PC] = pread_w(R[NUM_PCBP] + 4); | |
R[NUM_SP] = pread_w(R[NUM_PCBP] + 8); | |
if (R[NUM_PSW] & PSW_I_MASK) { | |
R[NUM_PSW] &= ~PSW_I_MASK; | |
R[NUM_PCBP] += 12; | |
} | |
/* set ISC to External Reset */ | |
R[NUM_PSW] &= ~PSW_ISC_MASK; | |
R[NUM_PSW] |= 3 << PSW_ISC ; | |
sim_debug(EXECUTE_MSG, &cpu_dev, | |
">>> CPU BOOT/RESET COMPLETE. PC=%08x SP=%08x\n", | |
R[NUM_PC], R[NUM_SP]); | |
return SCPE_OK; | |
} | |
t_stat cpu_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) | |
{ | |
uint32 uaddr = (uint32) addr; | |
uint8 value; | |
t_stat succ; | |
if (vptr == NULL) { | |
return SCPE_ARG; | |
} | |
if (sw & EX_V_FLAG) { | |
succ = examine(uaddr, &value); | |
*vptr = value; | |
return succ; | |
} else { | |
if (!(addr_is_rom(uaddr) || addr_is_mem(uaddr) || addr_is_io(uaddr))) { | |
*vptr = 0; | |
return SCPE_NXM; | |
} | |
*vptr = (uint32) pread_b(uaddr); | |
return SCPE_OK; | |
} | |
} | |
t_stat cpu_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw) | |
{ | |
uint32 uaddr = (uint32) addr; | |
if (sw & EX_V_FLAG) { | |
return deposit(uaddr, (uint8) val); | |
} else { | |
if (!(addr_is_rom(uaddr) || addr_is_mem(uaddr) || addr_is_io(uaddr))) { | |
return SCPE_NXM; | |
} | |
pwrite_b(uaddr, (uint8) val); | |
return SCPE_OK; | |
} | |
} | |
t_stat cpu_reset(DEVICE *dptr) | |
{ | |
int i; | |
if (!sim_is_running) { | |
/* Clear registers */ | |
for (i = 0; i < 16; i++) { | |
R[i] = 0; | |
} | |
/* Allocate memory */ | |
if (ROM == NULL) { | |
ROM = (uint32 *) calloc(BOOT_CODE_SIZE >> 2, sizeof(uint32)); | |
if (ROM == NULL) { | |
return SCPE_MEM; | |
} | |
memset(ROM, 0, BOOT_CODE_SIZE >> 2); | |
} | |
if (RAM == NULL) { | |
RAM = (uint32 *) calloc((size_t)(MEM_SIZE >> 2), sizeof(uint32)); | |
if (RAM == NULL) { | |
return SCPE_MEM; | |
} | |
memset(RAM, 0, (size_t)(MEM_SIZE >> 2)); | |
sim_vm_is_subroutine_call = cpu_is_pc_a_subroutine_call; | |
} | |
cpu_load_rom(); | |
} | |
abort_context = C_NONE; | |
cpu_nmi = FALSE; | |
cpu_hist_p = 0; | |
cpu_in_wait = FALSE; | |
sim_brk_types = SWMASK('E'); | |
sim_brk_dflt = SWMASK('E'); | |
return SCPE_OK; | |
} | |
static const char *cpu_next_caveats = | |
"The NEXT command in this 3B2 architecture simulator currently will\n" | |
"enable stepping across subroutine calls which are initiated by the\n" | |
"JSB, CALL and CALLPS instructions.\n" | |
"This stepping works by dynamically establishing breakpoints at the\n" | |
"memory address immediately following the instruction which initiated\n" | |
"the subroutine call. These dynamic breakpoints are automatically\n" | |
"removed once the simulator returns to the sim> prompt for any reason.\n" | |
"If the called routine returns somewhere other than one of these\n" | |
"locations due to a trap, stack unwind or any other reason, instruction\n" | |
"execution will continue until some other reason causes execution to stop.\n"; | |
t_bool cpu_is_pc_a_subroutine_call (t_addr **ret_addrs) | |
{ | |
static t_addr returns[MAX_SUB_RETURN_SKIP+1] = {0}; | |
static t_bool caveats_displayed = FALSE; | |
int i; | |
if (!caveats_displayed) { | |
caveats_displayed = TRUE; | |
sim_printf ("%s", cpu_next_caveats); | |
} | |
/* get data */ | |
if (SCPE_OK != get_aval (R[NUM_PC], &cpu_dev, &cpu_unit)) { | |
return FALSE; | |
} | |
switch (sim_eval[0]) { | |
case JSB: | |
case CALL: | |
case CALLPS: | |
returns[0] = R[NUM_PC] + (unsigned int) (1 - fprint_sym(stdnul, R[NUM_PC], | |
sim_eval, &cpu_unit, | |
SWMASK ('M'))); | |
for (i=1; i<MAX_SUB_RETURN_SKIP; i++) { | |
/* Possible skip return */ | |
returns[i] = returns[i-1] + 1; | |
} | |
returns[i] = 0; /* Make sure the address list ends with a zero */ | |
*ret_addrs = returns; | |
return TRUE; | |
default: | |
return FALSE; | |
} | |
} | |
t_stat cpu_set_hist(UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
uint32 i, size; | |
t_stat result; | |
if (cptr == NULL) { | |
/* Disable the feature */ | |
if (INST != NULL) { | |
for (i = 0; i < cpu_hist_size; i++) { | |
INST[i].valid = FALSE; | |
} | |
} | |
cpu_hist_size = 0; | |
cpu_hist_p = 0; | |
return SCPE_OK; | |
} | |
size = (uint32) get_uint(cptr, 10, MAX_HIST_SIZE, &result); | |
if ((result != SCPE_OK) || (size < MIN_HIST_SIZE)) { | |
return SCPE_ARG; | |
} | |
cpu_hist_p = 0; | |
if (size > 0) { | |
if (INST != NULL) { | |
free(INST); | |
} | |
INST = (instr *)calloc(size, sizeof(instr)); | |
if (INST == NULL) { | |
return SCPE_MEM; | |
} | |
memset(INST, 0, sizeof(instr) * size); | |
cpu_hist_size = size; | |
} | |
return SCPE_OK; | |
} | |
void fprint_sym_m(FILE *st, instr *ip) | |
{ | |
int32 i; | |
if (ip == NULL || ip->mn == NULL) { | |
fprintf(st, "???"); | |
return; | |
} | |
fprintf(st, "%s", ip->mn->mnemonic); | |
if (ip->mn->op_count > 0) { | |
fputc(' ', st); | |
} | |
/* Show the operand mnemonics */ | |
for (i = 0; i < ip->mn->op_count; i++) { | |
cpu_show_operand(st, &ip->operands[i]); | |
if (i < ip->mn->op_count - 1) { | |
fputc(',', st); | |
} | |
} | |
} | |
t_stat cpu_show_hist(FILE *st, UNIT *uptr, int32 val, CONST void *desc) | |
{ | |
uint32 i; | |
size_t j, count; | |
char *cptr = (char *) desc; | |
t_stat result; | |
instr *ip; | |
int32 di; | |
if (cpu_hist_size == 0) { | |
return SCPE_NOFNC; | |
} | |
/* 'count' is the number of history entries the user wants */ | |
if (cptr) { | |
count = (size_t) get_uint(cptr, 10, cpu_hist_size, &result); | |
if ((result != SCPE_OK) || (count == 0)) { | |
return SCPE_ARG; | |
} | |
} else { | |
count = cpu_hist_size; | |
} | |
/* Position for reading from ring buffer */ | |
di = (int32) (cpu_hist_p - count); | |
if (di < 0) { | |
di = di + (int32) cpu_hist_size; | |
} | |
fprintf(st, "PSW SP PC IR\n"); | |
for (i = 0; i < count; i++) { | |
ip = &INST[(di++) % (int32) cpu_hist_size]; | |
if (ip->valid) { | |
/* Show the opcode mnemonic */ | |
fprintf(st, "%08x %08x %08x ", ip->psw, ip->sp, ip->pc); | |
/* Show the operand data */ | |
if (ip->mn == NULL || ip->mn->op_count < 0) { | |
fprintf(st, "???"); | |
} else { | |
fprint_sym_m(st, ip); | |
if (ip->mn->op_count > 0 && ip->mn->mode == OP_DESC) { | |
fprintf(st, "\n "); | |
for (j = 0; j < (uint32) ip->mn->op_count; j++) { | |
fprintf(st, "%08x", ip->operands[j].data); | |
if (j < (uint32) ip->mn->op_count - 1) { | |
fputc(' ', st); | |
} | |
} | |
} | |
} | |
fputc('\n', st); | |
} | |
} | |
return SCPE_OK; | |
} | |
void cpu_register_name(uint8 reg, char *buf, size_t len) { | |
switch(reg) { | |
case 9: | |
snprintf(buf, len, "%%fp"); | |
break; | |
case 10: | |
snprintf(buf, len, "%%ap"); | |
break; | |
case 11: | |
snprintf(buf, len, "%%psw"); | |
break; | |
case 12: | |
snprintf(buf, len, "%%sp"); | |
break; | |
case 13: | |
snprintf(buf, len, "%%pcbp"); | |
break; | |
case 14: | |
snprintf(buf, len, "%%isp"); | |
break; | |
case 15: | |
snprintf(buf, len, "%%pc"); | |
break; | |
default: | |
snprintf(buf, len, "%%r%d", reg); | |
break; | |
} | |
} | |
void cpu_show_operand(FILE *st, operand *op) | |
{ | |
char reg_name[8]; | |
if (op->etype != -1) { | |
switch(op->etype) { | |
case 0: | |
fprintf(st, "{uword}"); | |
break; | |
case 2: | |
fprintf(st, "{uhalf}"); | |
break; | |
case 3: | |
fprintf(st, "{ubyte}"); | |
break; | |
case 4: | |
fprintf(st, "{word}"); | |
break; | |
case 6: | |
fprintf(st, "{half}"); | |
break; | |
case 7: | |
fprintf(st, "{sbyte}"); | |
break; | |
} | |
} | |
switch(op->mode) { | |
case 0: | |
case 1: | |
case 2: | |
case 3: | |
fprintf(st, "&0x%x", op->embedded.b); | |
break; | |
case 4: | |
if (op->reg == 15) { | |
fprintf(st, "&0x%x", op->embedded.w); | |
} else { | |
cpu_register_name(op->reg, reg_name, 8); | |
fprintf(st, "%s", reg_name); | |
} | |
break; | |
case 5: | |
if (op->reg == 15) { | |
fprintf(st, "&0x%x", op->embedded.w); | |
} else { | |
cpu_register_name(op->reg, reg_name, 8); | |
fprintf(st, "(%s)", reg_name); | |
} | |
break; | |
case 6: /* FP Short Offset */ | |
if (op->reg == 15) { | |
fprintf(st, "&0x%x", op->embedded.w); | |
} else { | |
fprintf(st, "%d(%%fp)", op->reg); | |
} | |
break; | |
case 7: /* AP Short Offset */ | |
if (op->reg == 15) { | |
fprintf(st, "$0x%x", op->embedded.w); | |
} else { | |
fprintf(st, "%d(%%ap)", op->embedded.w); | |
} | |
break; | |
case 8: | |
cpu_register_name(op->reg, reg_name, 8); | |
fprintf(st, "0x%x(%s)", (int32)op->embedded.w, reg_name); | |
break; | |
case 9: | |
cpu_register_name(op->reg, reg_name, 8); | |
fprintf(st, "*0x%x(%s)", (int32)op->embedded.w, reg_name); | |
break; | |
case 10: | |
cpu_register_name(op->reg, reg_name, 8); | |
fprintf(st, "0x%x(%s)", (int16)op->embedded.w, reg_name); | |
break; | |
case 11: | |
cpu_register_name(op->reg, reg_name, 8); | |
fprintf(st, "*0x%x(%s)", (int16)op->embedded.w, reg_name); | |
break; | |
case 12: | |
cpu_register_name(op->reg, reg_name, 8); | |
fprintf(st, "%d(%s)", (int8)op->embedded.w, reg_name); | |
break; | |
case 13: | |
cpu_register_name(op->reg, reg_name, 8); | |
fprintf(st, "*%d(%s)", (int8)op->embedded.w, reg_name); | |
break; | |
case 14: | |
if (op->reg == 15) { | |
fprintf(st, "*$0x%x", op->embedded.w); | |
} | |
break; | |
case 15: | |
fprintf(st, "&%d", (int32)op->embedded.w); | |
break; | |
} | |
} | |
t_stat cpu_set_size(UNIT *uptr, int32 val, CONST char *cptr, void *desc) | |
{ | |
uint32 *nRAM = NULL; | |
uint32 uval = (uint32) val; | |
if ((val <= 0) || (val > MAXMEMSIZE)) { | |
return SCPE_ARG; | |
} | |
/* Do (re-)allocation for memory. */ | |
nRAM = (uint32 *) calloc(uval >> 2, sizeof(uint32)); | |
if (nRAM == NULL) { | |
return SCPE_MEM; | |
} | |
free(RAM); | |
RAM = nRAM; | |
MEM_SIZE = uval; | |
memset(RAM, 0, (size_t)(MEM_SIZE >> 2)); | |
return SCPE_OK; | |
} | |
static SIM_INLINE void clear_instruction(instr *inst) | |
{ | |
uint8 i; | |
inst->mn = NULL; | |
inst->psw = 0; | |
inst->sp = 0; | |
inst->pc = 0; | |
for (i = 0; i < 4; i++) { | |
inst->operands[i].mode = 0; | |
inst->operands[i].reg = 0; | |
inst->operands[i].dtype = -1; | |
inst->operands[i].etype = -1; | |
inst->operands[i].embedded.w = 0; | |
inst->operands[i].data = 0; | |
} | |
} | |
/* | |
* Decode a single descriptor-defined operand from the instruction | |
* stream. Returns the number of bytes consumed during decode. | |
*/ | |
static uint8 decode_operand(uint32 pa, instr *instr, uint8 op_number) | |
{ | |
uint8 desc; | |
uint8 offset = 0; | |
operand *oper = &instr->operands[op_number]; | |
/* Set the default data type if none is already set */ | |
if (cpu_dtype == -1) { | |
cpu_dtype = instr->mn->dtype; | |
} | |
/* Read in the descriptor byte */ | |
desc = read_b(pa + offset++, ACC_OF); | |
oper->mode = (desc >> 4) & 0xf; | |
oper->reg = desc & 0xf; | |
oper->dtype = instr->mn->dtype; | |
oper->etype = cpu_etype; | |
switch (oper->mode) { | |
case 0: /* Positive Literal */ | |
case 1: /* Positive Literal */ | |
case 2: /* Positive Literal */ | |
case 3: /* Positive Literal */ | |
case 15: /* Negative literal */ | |
oper->embedded.b = (uint8)desc; | |
oper->data = oper->embedded.b; | |
break; | |
case 4: /* Word Immediate, Register Mode */ | |
switch (oper->reg) { | |
case 15: /* Word Immediate */ | |
oper->embedded.w = (uint32) read_b(pa + offset++, ACC_OF); | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 8u; | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 16u; | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 24u; | |
oper->data = oper->embedded.w; | |
break; | |
default: /* Register mode */ | |
oper->data = R[oper->reg]; | |
break; | |
} | |
break; | |
case 5: /* Halfword Immediate, Register Deferred Mode */ | |
switch (oper->reg) { | |
case 15: /* Halfword Immediate */ | |
oper->embedded.h = (uint16) read_b(pa + offset++, ACC_OF); | |
oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_OF)) << 8u; | |
oper->data = oper->embedded.h; | |
break; | |
case 11: /* INVALID */ | |
cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); | |
return offset; | |
default: /* Register deferred mode */ | |
oper->data = R[oper->reg]; | |
break; | |
} | |
break; | |
case 6: /* Byte Immediate, FP Short Offset */ | |
switch (oper->reg) { | |
case 15: /* Byte Immediate */ | |
oper->embedded.b = read_b(pa + offset++, ACC_OF); | |
oper->data = oper->embedded.b; | |
break; | |
default: /* FP Short Offset */ | |
oper->embedded.b = oper->reg; | |
oper->data = oper->embedded.b; | |
break; | |
} | |
break; | |
case 7: /* Absolute, AP Short Offset */ | |
switch (oper->reg) { | |
case 15: /* Absolute */ | |
oper->embedded.w = (uint32) read_b(pa + offset++, ACC_OF); | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 8u; | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 16u; | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 24u; | |
oper->data = oper->embedded.w; | |
break; | |
default: /* AP Short Offset */ | |
oper->embedded.b = oper->reg; | |
oper->data = oper->embedded.b; | |
break; | |
} | |
break; | |
case 8: /* Word Displacement */ | |
case 9: /* Word Displacement Deferred */ | |
oper->embedded.w = (uint32) read_b(pa + offset++, ACC_OF); | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 8u; | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 16u; | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 24u; | |
oper->data = oper->embedded.w; | |
break; | |
case 10: /* Halfword Displacement */ | |
case 11: /* Halfword Displacement Deferred */ | |
oper->embedded.h = read_b(pa + offset++, ACC_OF); | |
oper->embedded.h |= ((uint16) read_b(pa + offset++, ACC_OF)) << 8u; | |
oper->data = oper->embedded.h; | |
break; | |
case 12: /* Byte Displacement */ | |
case 13: /* Byte Displacement Deferred */ | |
oper->embedded.b = read_b(pa + offset++, ACC_OF); | |
oper->data = oper->embedded.b; | |
break; | |
case 14: /* Absolute Deferred, Extended-Operand */ | |
switch (oper->reg) { | |
case 15: /* Absolute Deferred */ | |
oper->embedded.w = (uint32) read_b(pa + offset++, ACC_OF); | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 8u; | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 16u; | |
oper->embedded.w |= ((uint32) read_b(pa + offset++, ACC_OF)) << 24u; | |
break; | |
case 0: | |
case 2: | |
case 3: | |
case 4: | |
case 6: | |
case 7: /* Expanded Datatype */ | |
/* Recursively decode the remainder of the operand after | |
storing the expanded datatype */ | |
cpu_etype = (int8) oper->reg; | |
oper->etype = cpu_etype; | |
offset += decode_operand(pa + offset, instr, op_number); | |
break; | |
default: | |
cpu_abort(NORMAL_EXCEPTION, RESERVED_DATATYPE); | |
break; | |
} | |
break; | |
default: | |
cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); | |
} | |
return offset; | |
} | |
/* | |
* Decode the instruction currently being pointed at by the PC. | |
* This routine does the following: | |
* 1. Read the opcode. | |
* 2. Determine the number of operands to decode based on | |
* the opcode type. | |
* 3. Fetch each opcode from main memory. | |
* | |
* This routine may alter the PSW's ET (Exception Type) and | |
* ISC (Internal State Code) registers if an exceptional condition | |
* is encountered during decode. | |
*/ | |
uint8 decode_instruction(instr *instr) | |
{ | |
uint8 offset = 0; | |
uint8 b1, b2; | |
uint16 hword_op; | |
uint32 pa; | |
mnemonic *mn = NULL; | |
int i; | |
pa = R[NUM_PC]; | |
/* Store off the PC and and PSW for history keeping */ | |
instr->psw = R[NUM_PSW]; | |
instr->sp = R[NUM_SP]; | |
instr->pc = pa; | |
/* Reset our data types */ | |
cpu_etype = -1; | |
cpu_dtype = -1; | |
if (read_operand(pa + offset++, &b1) != SCPE_OK) { | |
/* We tried to read out of a page that doesn't exist. We | |
need to let the operating system handle it.*/ | |
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); | |
return offset; | |
} | |
/* It should never, ever happen that operand fetch | |
would cause a page fault. */ | |
if (b1 == 0x30) { | |
read_operand(pa + offset++, &b2); | |
hword_op = (uint16) ((uint16)b1 << 8) | (uint16) b2; | |
for (i = 0; i < HWORD_OP_COUNT; i++) { | |
if (hword_ops[i].opcode == hword_op) { | |
mn = &hword_ops[i]; | |
break; | |
} | |
} | |
} else { | |
mn = &ops[b1]; | |
} | |
if (mn == NULL) { | |
cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); | |
return offset; | |
} | |
instr->mn = mn; | |
if (mn->op_count < 0) { | |
cpu_abort(NORMAL_EXCEPTION, ILLEGAL_OPCODE); | |
return offset; | |
} | |
if (mn->op_count == 0) { | |
/* Nothing else to do, we're done decoding. */ | |
return offset; | |
} | |
switch (mn->mode) { | |
case OP_NONE: | |
break; | |
case OP_BYTE: | |
instr->operands[0].embedded.b = read_b(pa + offset++, ACC_OF); | |
instr->operands[0].mode = 6; | |
instr->operands[0].reg = 15; | |
break; | |
case OP_HALF: | |
instr->operands[0].embedded.h = read_b(pa + offset++, ACC_OF); | |
instr->operands[0].embedded.h |= read_b(pa + offset++, ACC_OF) << 8; | |
instr->operands[0].mode = 5; | |
instr->operands[0].reg = 15; | |
break; | |
case OP_COPR: | |
instr->operands[0].embedded.w = (uint32) read_b(pa + offset++, ACC_OF); | |
instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_OF) << 8; | |
instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_OF) << 16; | |
instr->operands[0].embedded.w |= (uint32) read_b(pa + offset++, ACC_OF) << 24; | |
instr->operands[0].mode = 4; | |
instr->operands[0].reg = 15; | |
/* Decode subsequent operands */ | |
for (i = 1; i < mn->op_count; i++) { | |
offset += decode_operand(pa + offset, instr, (uint8) i); | |
} | |
break; | |
case OP_DESC: | |
for (i = 0; i < mn->op_count; i++) { | |
offset += decode_operand(pa + offset, instr, (uint8) i); | |
} | |
break; | |
} | |
return offset; | |
} | |
static SIM_INLINE void cpu_context_switch_3(uint32 new_pcbp) | |
{ | |
if (R[NUM_PSW] & PSW_R_MASK) { | |
R[0] = R[NUM_PCBP] + 64; | |
R[2] = read_w(R[0], ACC_AF); | |
R[0] += 4; | |
while (R[2] != 0) { | |
R[1] = read_w(R[0], ACC_AF); | |
R[0] += 4; | |
/* Execute MOVBLW instruction inside this loop */ | |
while (R[2] != 0) { | |
write_w(R[1], read_w(R[0], ACC_AF)); | |
R[2]--; | |
R[0] += 4; | |
R[1] += 4; | |
} | |
R[2] = read_w(R[0], ACC_AF); | |
R[0] += 4; | |
} | |
R[0] = R[0] + 4; | |
} | |
} | |
static SIM_INLINE void cpu_context_switch_2(uint32 new_pcbp) | |
{ | |
R[NUM_PCBP] = new_pcbp; | |
/* Put new PSW, PC and SP values from PCB into registers */ | |
R[NUM_PSW] = read_w(R[NUM_PCBP], ACC_AF); | |
R[NUM_PSW] &= ~PSW_TM_MASK; /* Clear TM */ | |
R[NUM_PC] = read_w(R[NUM_PCBP] + 4, ACC_AF); | |
R[NUM_SP] = read_w(R[NUM_PCBP] + 8, ACC_AF); | |
/* If i-bit is set, increment PCBP past initial context area */ | |
if (R[NUM_PSW] & PSW_I_MASK) { | |
R[NUM_PSW] &= ~PSW_I_MASK; | |
R[NUM_PCBP] += 12; | |
} | |
} | |
static SIM_INLINE void cpu_context_switch_1(uint32 new_pcbp) | |
{ | |
/* Save the current PC in PCB */ | |
write_w(R[NUM_PCBP] + 4, R[NUM_PC]); | |
/* Copy the 'R' flag from the new PSW to the old PSW */ | |
R[NUM_PSW] &= ~PSW_R_MASK; | |
R[NUM_PSW] |= (read_w(new_pcbp, ACC_AF) & PSW_R_MASK); | |
/* Save current PSW and SP in PCB */ | |
write_w(R[NUM_PCBP], R[NUM_PSW]); | |
write_w(R[NUM_PCBP] + 8, R[NUM_SP]); | |
/* If R is set, save current R0-R8/FP/AP in PCB */ | |
if (R[NUM_PSW] & PSW_R_MASK) { | |
write_w(R[NUM_PCBP] + 24, R[NUM_FP]); | |
write_w(R[NUM_PCBP] + 28, R[0]); | |
write_w(R[NUM_PCBP] + 32, R[1]); | |
write_w(R[NUM_PCBP] + 36, R[2]); | |
write_w(R[NUM_PCBP] + 40, R[3]); | |
write_w(R[NUM_PCBP] + 44, R[4]); | |
write_w(R[NUM_PCBP] + 48, R[5]); | |
write_w(R[NUM_PCBP] + 52, R[6]); | |
write_w(R[NUM_PCBP] + 56, R[7]); | |
write_w(R[NUM_PCBP] + 60, R[8]); | |
write_w(R[NUM_PCBP] + 20, R[NUM_AP]); | |
R[NUM_FP] = R[NUM_PCBP] + 52; | |
} | |
} | |
t_bool cpu_on_interrupt(uint8 ipl) | |
{ | |
uint32 new_pcbp; | |
uint16 id = ipl; /* TODO: Does this need to be uint16? */ | |
sim_debug(IRQ_MSG, &cpu_dev, | |
"[%08x] [cpu_on_interrupt] ipl=%d\n", | |
R[NUM_PC], ipl); | |
/* | |
* "If a nonmaskable interrupt request is received, an auto-vector | |
* interrupt acknowledge cycle is performed (as if an autovector | |
* interrupt at level 0 was being acknowledged) and no | |
* Interrupt-ID is fetched. The value 0 is used as the ID." | |
*/ | |
if (cpu_nmi) { | |
id = 0; | |
} | |
cpu_km = TRUE; | |
if ((R[NUM_PSW] & PSW_QIE_MASK) >> PSW_QIE) { | |
/* TODO: Maybe implement quick interrupts at some point, but | |
the 3B2 ROM and SVR3 don't appear to use them. */ | |
assert(0); | |
} | |
new_pcbp = read_w(0x8c + (4 * id), ACC_AF); | |
/* Save the old PCBP */ | |
irq_push_word(R[NUM_PCBP]); | |
/* Set ISC, TM, and ET to 0, 0, 1 before saving */ | |
R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); | |
R[NUM_PSW] |= (1 << PSW_ET); | |
/* Context switch */ | |
cpu_context_switch_1(new_pcbp); | |
cpu_context_switch_2(new_pcbp); | |
/* Set ISC, TM, and ET to 7, 0, 3 in new PSW */ | |
R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); | |
R[NUM_PSW] |= (7 << PSW_ISC); | |
R[NUM_PSW] |= (3 << PSW_ET); | |
cpu_context_switch_3(new_pcbp); | |
cpu_km = FALSE; | |
return TRUE; | |
} | |
t_stat sim_instr(void) | |
{ | |
uint8 et, isc; | |
/* Temporary register used for overflow detection */ | |
t_uint64 result; | |
/* Scratch space */ | |
uint32 a, b, c, d; | |
/* Used for field calculation */ | |
uint32 width, offset; | |
t_uint64 mask; | |
operand *src1, *src2, *src3, *dst; | |
stop_reason = 0; | |
abort_reason = (uint32) setjmp(save_env); | |
/* Exception handler. | |
* | |
* This gets a little messy because of exception contexts. If a | |
* normal-exception happens while we're handling a | |
* normal-exception, it needs to be treated as a stack-exception. | |
*/ | |
if (abort_reason != 0) { | |
if (cpu_exception_stack_depth++ >= 10) { | |
return STOP_ESTK; | |
} | |
if (cpu_unit.flags & UNIT_EXHALT) { | |
return STOP_EX; | |
} | |
if (abort_reason == ABORT_EXC) { | |
et = R[NUM_PSW] & PSW_ET_MASK; | |
isc = (R[NUM_PSW] & PSW_ISC_MASK) >> PSW_ISC; | |
switch(abort_context) { | |
case C_NORMAL_GATE_VECTOR: | |
cpu_on_normal_exception(N_GATE_VECTOR); | |
break; | |
case C_PROCESS_GATE_PCB: | |
cpu_on_process_exception(GATE_PCB_FAULT); | |
break; | |
case C_PROCESS_OLD_PCB: | |
cpu_on_process_exception(OLD_PCB_FAULT); | |
break; | |
case C_PROCESS_NEW_PCB: | |
cpu_on_process_exception(NEW_PCB_FAULT); | |
break; | |
case C_STACK_FAULT: | |
cpu_on_stack_exception(STACK_FAULT); | |
break; | |
case C_RESET_GATE_VECTOR: | |
cpu_on_reset_exception(GATE_VECTOR_FAULT); | |
break; | |
case C_RESET_SYSTEM_DATA: | |
cpu_on_reset_exception(SYSTEM_DATA_FAULT); | |
break; | |
case C_RESET_INT_STACK: | |
cpu_on_reset_exception(INTERRUPT_STACK_FAULT); | |
break; | |
default: | |
switch(et) { | |
case NORMAL_EXCEPTION: | |
cpu_on_normal_exception(isc); | |
break; | |
case STACK_EXCEPTION: | |
cpu_on_stack_exception(isc); | |
break; | |
case RESET_EXCEPTION: | |
cpu_on_reset_exception(isc); | |
break; | |
default: | |
stop_reason = STOP_EX; | |
break; | |
} | |
break; | |
} | |
} else { | |
/* TODO: Handle traps */ | |
stop_reason = STOP_EX; | |
} | |
} | |
while (stop_reason == 0) { | |
abort_context = C_NONE; | |
if (sim_brk_summ && sim_brk_test(R[NUM_PC], SWMASK ('E'))) { | |
stop_reason = STOP_IBKPT; | |
break; | |
} | |
if (cpu_exception_stack_depth > 0) { | |
cpu_exception_stack_depth--; | |
} | |
if (sim_interval-- <= 0) { | |
if ((stop_reason = sim_process_event())) { | |
break; | |
} | |
} | |
/* Process DMA requests */ | |
dmac_service_drqs(); | |
/* | |
* Post-increment IU mode pointers (if needed). | |
* | |
* This is essentially a colossal hack. We never want to | |
* increment these pointers during an interlocked Read/Write | |
* operation, so we only increment after a CPU step has | |
* occured. | |
*/ | |
if (iu_increment_a) { | |
increment_modep_a(); | |
} | |
if (iu_increment_b) { | |
increment_modep_b(); | |
} | |
/* Process pending IRQ, if applicable */ | |
if (PSW_CUR_IPL < cpu_ipl()) { | |
cpu_on_interrupt(cpu_ipl()); | |
cpu_nmi = FALSE; | |
cpu_in_wait = FALSE; | |
continue; | |
} | |
if (cpu_in_wait) { | |
if (sim_idle_enab) { | |
sim_idle(TMR_CLK, TRUE); | |
} | |
continue; | |
} | |
/* Reset the TM bits */ | |
R[NUM_PSW] &= ~PSW_TM; | |
R[NUM_PSW] |= PSW_TM_MASK; | |
/* Record the instruction for history */ | |
if (cpu_hist_size > 0) { | |
cpu_instr = &INST[cpu_hist_p]; | |
cpu_hist_p = (cpu_hist_p + 1) % cpu_hist_size; | |
} else { | |
cpu_instr = &inst; | |
} | |
/* Decode the instruction */ | |
memset(cpu_instr, 0, sizeof(instr)); | |
cpu_ilen = decode_instruction(cpu_instr); | |
/* Make sure to update the valid bit for history keeping (if | |
* enabled) */ | |
cpu_instr->valid = TRUE; | |
/* | |
* Operate on the decoded instruction. | |
*/ | |
/* Get the operands */ | |
if (cpu_instr->mn->src_op1 >= 0) { | |
src1 = &cpu_instr->operands[cpu_instr->mn->src_op1]; | |
} | |
if (cpu_instr->mn->src_op2 >= 0) { | |
src2 = &cpu_instr->operands[cpu_instr->mn->src_op2]; | |
} | |
if (cpu_instr->mn->src_op3 >= 0) { | |
src3 = &cpu_instr->operands[cpu_instr->mn->src_op3]; | |
} | |
if (cpu_instr->mn->dst_op >= 0) { | |
dst = &cpu_instr->operands[cpu_instr->mn->dst_op]; | |
} | |
switch (cpu_instr->mn->opcode) { | |
case ADDW2: | |
case ADDH2: | |
case ADDB2: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(dst); | |
add(a, b, dst); | |
break; | |
case ADDW3: | |
case ADDH3: | |
case ADDB3: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
add(a, b, dst); | |
break; | |
case ALSW3: | |
a = cpu_read_op(src2); | |
b = cpu_read_op(src1); | |
result = (t_uint64)a << (b & 0x1f); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case ANDW2: | |
case ANDH2: | |
case ANDB2: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(dst); | |
c = a & b; | |
cpu_write_op(dst, c); | |
cpu_set_nz_flags(c, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(c, dst); | |
break; | |
case ANDW3: | |
case ANDH3: | |
case ANDB3: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
c = a & b; | |
cpu_write_op(dst, c); | |
cpu_set_nz_flags(c, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(c, dst); | |
break; | |
case BEH: | |
case BEH_D: | |
if (cpu_z_flag() == 1) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BEB: | |
case BEB_D: | |
if (cpu_z_flag() == 1) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BGH: | |
if ((cpu_n_flag() | cpu_z_flag()) == 0) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BGB: | |
if ((cpu_n_flag() | cpu_z_flag()) == 0) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BGEH: | |
if ((cpu_n_flag() == 0) | (cpu_z_flag() == 1)) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BGEB: | |
if ((cpu_n_flag() == 0) | (cpu_z_flag() == 1)) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BGEUH: | |
if (cpu_c_flag() == 0) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BGEUB: | |
if (cpu_c_flag() == 0) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BGUH: | |
if ((cpu_c_flag() | cpu_z_flag()) == 0) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BGUB: | |
if ((cpu_c_flag() | cpu_z_flag()) == 0) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BITW: | |
case BITH: | |
case BITB: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
c = a & b; | |
cpu_set_nz_flags(c, src1); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag(0); | |
break; | |
case BLH: | |
if ((cpu_n_flag() == 1) && (cpu_z_flag() == 0)) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BLB: | |
if ((cpu_n_flag() == 1) && (cpu_z_flag() == 0)) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BLEH: | |
if ((cpu_n_flag() | cpu_z_flag()) == 1) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BLEB: | |
if ((cpu_n_flag() | cpu_z_flag()) == 1) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BLEUH: | |
if ((cpu_c_flag() | cpu_z_flag()) == 1) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BLEUB: | |
if ((cpu_c_flag() | cpu_z_flag()) == 1) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BLUH: | |
if (cpu_c_flag() == 1) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BLUB: | |
if (cpu_c_flag() == 1) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BNEH: | |
case BNEH_D: | |
if (cpu_z_flag() == 0) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BNEB: | |
case BNEB_D: | |
if (cpu_z_flag() == 0) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BPT: | |
/* TODO: Confirm that a breakpoint trap will increment the | |
PC. Otherwise, change 'break' to 'continue' */ | |
cpu_abort(NORMAL_EXCEPTION, BREAKPOINT_TRAP); | |
break; | |
case BRH: | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
case BRB: | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
case BSBH: | |
cpu_push_word(R[NUM_PC] + cpu_ilen); | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
case BSBB: | |
cpu_push_word(R[NUM_PC] + cpu_ilen); | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
case BVCH: | |
if (cpu_v_flag() == 0) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BVCB: | |
if (cpu_v_flag() == 0) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case BVSH: | |
if (cpu_v_flag() == 1) { | |
R[NUM_PC] += sign_extend_h(dst->embedded.h); | |
continue; | |
} | |
break; | |
case BVSB: | |
if (cpu_v_flag() == 1) { | |
R[NUM_PC] += sign_extend_b(dst->embedded.b); | |
continue; | |
} | |
break; | |
case CALL: | |
a = cpu_effective_address(src1); | |
b = cpu_effective_address(dst); | |
write_w(R[NUM_SP] + 4, R[NUM_AP]); | |
write_w(R[NUM_SP], R[NUM_PC] + cpu_ilen); | |
R[NUM_SP] += 8; | |
R[NUM_PC] = b; | |
R[NUM_AP] = a; | |
continue; | |
case CFLUSH: | |
break; | |
case CALLPS: | |
if (cpu_execution_level() != EX_LVL_KERN) { | |
cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); | |
break; | |
} | |
a = R[0]; | |
cpu_km = TRUE; | |
abort_context = C_RESET_INT_STACK; | |
irq_push_word(R[NUM_PCBP]); | |
/* Set current PC to start of next instruction (always PC+2) */ | |
R[NUM_PC] += 2; | |
/* Set old PSW ISC, TM, and ET to 0, 0, 1 */ | |
R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); | |
R[NUM_PSW] |= (1 << PSW_ET); | |
cpu_context_switch_1(a); | |
cpu_context_switch_2(a); | |
R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); | |
R[NUM_PSW] |= (7 << PSW_ISC); | |
R[NUM_PSW] |= (3 << PSW_ET); | |
cpu_context_switch_3(a); | |
abort_context = C_NONE; | |
cpu_km = FALSE; | |
continue; | |
case CLRW: | |
case CLRH: | |
case CLRB: | |
cpu_write_op(dst, 0); | |
cpu_set_n_flag(0); | |
cpu_set_z_flag(1); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag(0); | |
break; | |
case CMPW: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
cpu_set_z_flag((uint32)b == (uint32)a); | |
cpu_set_n_flag((int32)b < (int32)a); | |
cpu_set_c_flag((uint32)b < (uint32)a); | |
cpu_set_v_flag(0); | |
break; | |
case CMPH: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
cpu_set_z_flag((uint16)b == (uint16)a); | |
cpu_set_n_flag((int16)b < (int16)a); | |
cpu_set_c_flag((uint16)b < (uint16)a); | |
cpu_set_v_flag(0); | |
break; | |
case CMPB: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
cpu_set_z_flag((uint8)b == (uint8)a); | |
cpu_set_n_flag((int8)b < (int8)a); | |
cpu_set_c_flag((uint8)b < (uint8)a); | |
cpu_set_v_flag(0); | |
break; | |
case DECW: | |
case DECH: | |
case DECB: | |
a = cpu_read_op(dst); | |
sub(a, 1, dst); | |
break; | |
case DIVW2: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(dst); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
if (a == WORD_MASK && b == WD_MSB) { | |
cpu_set_v_flag(1); | |
} | |
DIV(a, b, src1, dst, int32); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
break; | |
case DIVH2: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(dst); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
if (a == HALF_MASK && b == HW_MSB) { | |
cpu_set_v_flag(1); | |
} | |
DIV(a, b, src1, dst, int16); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
break; | |
case DIVB2: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(dst); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
if (a == BYTE_MASK && b == BT_MSB) { | |
cpu_set_v_flag(1); | |
} | |
result = (uint8)b / (uint8)a; | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
break; | |
case DIVW3: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
if (a == WORD_MASK && b == WD_MSB) { | |
cpu_set_v_flag(1); | |
} | |
DIV(a, b, src1, src2, int32); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
break; | |
case DIVH3: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
if (a == HALF_MASK && b == HW_MSB) { | |
cpu_set_v_flag(1); | |
} | |
DIV(a, b, src1, src2, int16); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
break; | |
case DIVB3: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
if (a == BYTE_MASK && b == BT_MSB) { | |
cpu_set_v_flag(1); | |
} | |
result = (uint8)b / (uint8)a; | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
break; | |
case MVERNO: | |
R[0] = WE32100_VER; | |
break; | |
case ENBVJMP: | |
if (cpu_execution_level() != EX_LVL_KERN) { | |
cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); | |
break; | |
} | |
mmu_enable(); | |
R[NUM_PC] = R[0]; | |
continue; | |
case DISVJMP: | |
if (cpu_execution_level() != EX_LVL_KERN) { | |
cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); | |
break; | |
} | |
mmu_disable(); | |
R[NUM_PC] = R[0]; | |
continue; | |
case EXTFW: | |
case EXTFH: | |
case EXTFB: | |
width = (cpu_read_op(src1) & 0x1f) + 1; | |
offset = cpu_read_op(src2) & 0x1f; | |
mask = (1ul << width) - 1; | |
mask = (mask << offset) & WORD_MASK; | |
if (width + offset > 32) { | |
mask |= (1ul << ((width + offset) - 32)) - 1; | |
} | |
a = cpu_read_op(src3); /* src */ | |
a &= mask; | |
a = a >> offset; | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
break; | |
case INCW: | |
case INCH: | |
case INCB: | |
a = cpu_read_op(dst); | |
add(a, 1, dst); | |
break; | |
case INSFW: | |
case INSFH: | |
case INSFB: | |
width = (cpu_read_op(src1) & 0x1f) + 1; | |
offset = cpu_read_op(src2) & 0x1f; | |
mask = ((1ul << width) - 1) & WORD_MASK; | |
a = cpu_read_op(src3) & mask; /* src */ | |
b = cpu_read_op(dst); /* dst */ | |
b &= ~(mask << offset); | |
b |= (a << offset); | |
cpu_write_op(dst, b); | |
cpu_set_nz_flags(b, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(b, dst); | |
break; | |
case JMP: | |
R[NUM_PC] = cpu_effective_address(dst); | |
continue; | |
case JSB: | |
cpu_push_word(R[NUM_PC] + cpu_ilen); | |
R[NUM_PC] = cpu_effective_address(dst); | |
continue; | |
case LLSW3: | |
result = (t_uint64)cpu_read_op(src2) << (cpu_read_op(src1) & 0x1f); | |
cpu_write_op(dst, (uint32)(result & WORD_MASK)); | |
cpu_set_nz_flags((uint32)(result & WORD_MASK), dst); | |
break; | |
case LLSH3: | |
a = cpu_read_op(src2) << (cpu_read_op(src1) & 0x1f); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
break; | |
case LLSB3: | |
a = cpu_read_op(src2) << (cpu_read_op(src1) & 0x1f); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
break; | |
case ARSW3: | |
case ARSH3: | |
case ARSB3: | |
a = cpu_read_op(src2); | |
b = cpu_read_op(src1) & 0x1f; | |
result = a >> b; | |
/* Ensure the MSB is copied appropriately */ | |
switch (op_type(src2)) { | |
case WD: | |
if (a & 0x80000000) { | |
result |= shift_32_table[b + 1]; | |
} | |
break; | |
case HW: | |
if (a & 0x8000) { | |
result |= shift_16_table[b + 1]; | |
} | |
break; | |
case BT: | |
if (a & 0x80) { | |
result |= shift_8_table[b + 1]; | |
} | |
break; | |
} | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag(0); | |
break; | |
case LRSW3: | |
a = (uint32) cpu_read_op(src2) >> (cpu_read_op(src1) & 0x1f); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
break; | |
case GATE: | |
cpu_km = TRUE; | |
abort_context = C_PROCESS_GATE_PCB; | |
if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF) || | |
R[NUM_SP] >= read_w(R[NUM_PCBP] + 16, ACC_AF)) { | |
sim_debug(EXECUTE_MSG, &cpu_dev, | |
"[%08x] STACK OUT OF BOUNDS IN GATE. " | |
"SP=%08x, R[NUM_PCBP]+12=%08x, " | |
"R[NUM_PCBP]+16=%08x\n", | |
R[NUM_PC], | |
R[NUM_SP], | |
read_w(R[NUM_PCBP] + 12, ACC_AF), | |
read_w(R[NUM_PCBP] + 16, ACC_AF)); | |
cpu_abort(STACK_EXCEPTION, STACK_BOUND); | |
} | |
cpu_km = FALSE; | |
abort_context = C_STACK_FAULT; | |
/* Push PC+2 onto stack */ | |
write_w(R[NUM_SP], R[NUM_PC] + 2); | |
/* Write 1, 0, 2 to ISC, TM, ET */ | |
R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); | |
R[NUM_PSW] |= (1 << PSW_ISC); | |
R[NUM_PSW] |= (2 << PSW_ET); | |
/* Push PSW onto stack */ | |
write_w(R[NUM_SP] + 4, R[NUM_PSW]); | |
abort_context = C_NONE; | |
/* Perform gate entry-point 2 */ | |
cpu_perform_gate(R[0] & 0x7c, | |
R[1] & 0x7ff8); | |
/* Finish push of PC and PSW */ | |
R[NUM_SP] += 8; | |
continue; | |
case MCOMW: | |
case MCOMH: | |
case MCOMB: | |
a = ~(cpu_read_op(src1)); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
break; | |
case MNEGW: | |
case MNEGH: | |
case MNEGB: | |
a = ~cpu_read_op(src1) + 1; | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
break; | |
case MOVBLW: | |
while (R[2] != 0) { | |
a = read_w(R[0], ACC_AF); | |
write_w(R[1], a); | |
R[2]--; | |
R[0] += 4; | |
R[1] += 4; | |
} | |
break; | |
case STREND: | |
while (read_b(R[0], ACC_AF) != 0) { | |
R[0]++; | |
} | |
break; | |
case SWAPWI: | |
case SWAPHI: | |
case SWAPBI: | |
a = cpu_read_op(dst); | |
cpu_write_op(dst, R[0]); | |
R[0] = a; | |
cpu_set_nz_flags(a, dst); | |
cpu_set_v_flag(0); | |
cpu_set_c_flag(0); | |
break; | |
case ROTW: | |
a = cpu_read_op(src1) & 31; | |
b = (uint32) cpu_read_op(src2); | |
mask = (CHAR_BIT * sizeof(a) - 1); | |
d = (b >> a) | (b << ((~a + 1) & mask)); | |
cpu_write_op(dst, d); | |
cpu_set_nz_flags(d, dst); | |
cpu_set_v_flag(0); | |
cpu_set_c_flag(0); | |
break; | |
case MOVAW: | |
a = cpu_effective_address(src1); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_v_flag(0); | |
cpu_set_c_flag(0); | |
break; | |
case MOVTRW: | |
a = cpu_effective_address(src1); | |
result = mmu_xlate_addr(a, ACC_MT); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_v_flag(0); | |
cpu_set_c_flag(0); | |
break; | |
case MOVW: | |
case MOVH: | |
case MOVB: | |
a = cpu_read_op(src1); | |
cpu_write_op(dst, a); | |
/* Flags are never set if the source or destination is the | |
PSW */ | |
if (!(op_is_psw(src1) || op_is_psw(dst))) { | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
} | |
/* However, if a move to PSW set the O bit, we have to | |
generate an overflow exception trap */ | |
if (op_is_psw(dst) && (R[NUM_PSW] & PSW_OE_MASK)) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_OVERFLOW); | |
} | |
break; | |
case MODW2: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(dst); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
MOD(a, b, src1, dst, int32); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case MODH2: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(dst); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
MOD(a, b, src1, dst, int16); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case MODB2: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(dst); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
result = (uint8)b % (uint8)a; | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
break; | |
case MODW3: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
MOD(a, b, src1, src2, int32); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case MODH3: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
MOD(a, b, src1, src2, int16); | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case MODB3: | |
a = cpu_read_op(src1); | |
b = cpu_read_op(src2); | |
if (a == 0) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_ZERO_DIVIDE); | |
break; | |
} | |
result = (uint8)b % (uint8)a; | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case MULW2: | |
result = (t_uint64)cpu_read_op(src1) * (t_uint64)cpu_read_op(dst); | |
cpu_write_op(dst, (uint32)(result & WORD_MASK)); | |
cpu_set_nz_flags((uint32)(result & WORD_MASK), dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case MULH2: | |
a = cpu_read_op(src1) * cpu_read_op(dst); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case MULB2: | |
a = cpu_read_op(src1) * cpu_read_op(dst); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, src1); | |
break; | |
case MULW3: | |
result = (t_uint64)cpu_read_op(src1) * (t_uint64)cpu_read_op(src2); | |
cpu_write_op(dst, (uint32)(result & WORD_MASK)); | |
cpu_set_nz_flags((uint32)(result & WORD_MASK), dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case MULH3: | |
a = cpu_read_op(src1) * cpu_read_op(src2); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case MULB3: | |
a = cpu_read_op(src1) * cpu_read_op(src2); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(result, dst); | |
break; | |
case NOP: | |
break; | |
case NOP2: | |
cpu_ilen += 1; | |
break; | |
case NOP3: | |
cpu_ilen += 2; | |
break; | |
case ORW2: | |
case ORH2: | |
case ORB2: | |
a = (cpu_read_op(src1) | cpu_read_op(dst)); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
break; | |
case ORW3: | |
case ORH3: | |
case ORB3: | |
a = (cpu_read_op(src1) | cpu_read_op(src2)); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
break; | |
case POPW: | |
/* N.B. "If dst is the stack pointer (%sp), the results | |
are indeterminate". The ordering here is important. If | |
we decrement SP before writing the results, we end up | |
in a weird, bad state. */ | |
a = read_w(R[NUM_SP] - 4, ACC_AF); | |
cpu_write_op(dst, a); | |
R[NUM_SP] -= 4; | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag(0); | |
break; | |
case PUSHAW: | |
a = cpu_effective_address(src1); | |
cpu_push_word(a); | |
cpu_set_nz_flags(a, src1); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag(0); | |
break; | |
case PUSHW: | |
a = cpu_read_op(src1); | |
cpu_push_word(a); | |
cpu_set_nz_flags(a, src1); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag(0); | |
break; | |
case RGEQ: | |
if (cpu_n_flag() == 0 || cpu_z_flag() == 1) { | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
} | |
break; | |
case RGEQU: | |
if (cpu_c_flag() == 0) { | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
} | |
break; | |
case RGTR: | |
if ((cpu_n_flag() | cpu_z_flag()) == 0) { | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
} | |
break; | |
case RNEQ: | |
case RNEQU: | |
if (cpu_z_flag() == 0) { | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
} | |
break; | |
case RET: | |
a = R[NUM_AP]; | |
b = read_w(R[NUM_SP] - 4, ACC_AF); | |
c = read_w(R[NUM_SP] - 8, ACC_AF); | |
R[NUM_AP] = b; | |
R[NUM_PC] = c; | |
R[NUM_SP] = a; | |
continue; | |
case RETG: | |
abort_context = C_STACK_FAULT; | |
a = read_w(R[NUM_SP] - 4, ACC_AF); /* PSW */ | |
b = read_w(R[NUM_SP] - 8, ACC_AF); /* PC */ | |
abort_context = C_NONE; | |
if ((a & PSW_CM_MASK) < (R[NUM_PSW] & PSW_CM_MASK)) { | |
sim_debug(EXECUTE_MSG, &cpu_dev, | |
"[%08x] Illegal level change. New level=%d, Cur level=%d\n", | |
R[NUM_PC], | |
(a & PSW_CM_MASK) >> PSW_CM, | |
(R[NUM_PSW] & PSW_CM_MASK) >> PSW_CM); | |
cpu_abort(NORMAL_EXCEPTION, ILLEGAL_LEVEL_CHANGE); | |
break; | |
} | |
/* Clear some state and move it from the current PSW */ | |
a &= ~PSW_IPL_MASK; | |
a &= ~PSW_CFD_MASK; | |
a &= ~PSW_QIE_MASK; | |
a &= ~PSW_CD_MASK; | |
a &= ~PSW_R_MASK; | |
a &= ~PSW_ISC_MASK; | |
a &= ~PSW_TM_MASK; | |
a &= ~PSW_ET_MASK; | |
a |= (R[NUM_PSW] & PSW_IPL_MASK); | |
a |= (R[NUM_PSW] & PSW_CFD_MASK); | |
a |= (R[NUM_PSW] & PSW_QIE_MASK); | |
a |= (R[NUM_PSW] & PSW_CD_MASK); | |
a |= (R[NUM_PSW] & PSW_R_MASK); | |
a |= (7 << PSW_ISC); | |
a |= (3 << PSW_ET); | |
R[NUM_PSW] = a; | |
R[NUM_PC] = b; | |
R[NUM_SP] -= 8; | |
continue; | |
case RETPS: | |
if (cpu_execution_level() != EX_LVL_KERN) { | |
cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_OPCODE); | |
break; | |
} | |
/* Force kernel memory access */ | |
cpu_km = TRUE; | |
abort_context = C_RESET_INT_STACK; | |
/* Restore process state */ | |
a = irq_pop_word(); /* New process PCBP */ | |
abort_context = C_PROCESS_OLD_PCB; | |
b = read_w(a, ACC_AF); /* New PSW */ | |
abort_context = C_PROCESS_NEW_PCB; | |
/* Copy the 'R' flag from the new PSW to the old PSW */ | |
R[NUM_PSW] &= ~PSW_R_MASK; | |
R[NUM_PSW] |= (b & PSW_R_MASK); | |
/* a now holds the new PCBP */ | |
cpu_context_switch_2(a); | |
/* Perform block moves, if any */ | |
cpu_context_switch_3(a); | |
/* Restore registers if R bit is set */ | |
if (R[NUM_PSW] & PSW_R_MASK) { | |
R[NUM_FP] = read_w(a + 24, ACC_AF); | |
R[0] = read_w(a + 28, ACC_AF); | |
R[1] = read_w(a + 32, ACC_AF); | |
R[2] = read_w(a + 36, ACC_AF); | |
R[3] = read_w(a + 40, ACC_AF); | |
R[4] = read_w(a + 44, ACC_AF); | |
R[5] = read_w(a + 48, ACC_AF); | |
R[6] = read_w(a + 52, ACC_AF); | |
R[7] = read_w(a + 56, ACC_AF); | |
R[8] = read_w(a + 60, ACC_AF); | |
R[NUM_AP] = read_w(a + 20, ACC_AF); | |
} | |
abort_context = C_NONE; | |
/* Un-force kernel memory access */ | |
cpu_km = FALSE; | |
continue; | |
case SPOP: | |
case SPOPRD: | |
case SPOPRS: | |
/* Memory fault is signaled when no support processor is | |
active */ | |
cpu_abort(NORMAL_EXCEPTION, EXTERNAL_MEMORY_FAULT); | |
break; | |
case SUBW2: | |
case SUBH2: | |
case SUBB2: | |
a = cpu_read_op(dst); | |
b = cpu_read_op(src1); | |
sub(a, b, dst); | |
break; | |
case SUBW3: | |
case SUBH3: | |
case SUBB3: | |
a = cpu_read_op(src2); | |
b = cpu_read_op(src1); | |
sub(a, b, dst); | |
break; | |
case RESTORE: | |
a = R[NUM_FP] - 28; /* Old FP */ | |
b = read_w(a, ACC_AF); /* Old FP */ | |
c = R[NUM_FP] - 24; /* Old save point */ | |
for (d = src1->reg; d < NUM_FP; d++) { | |
R[d] = read_w(c, ACC_AF); | |
c += 4; | |
} | |
R[NUM_FP] = b; /* Restore FP */ | |
R[NUM_SP] = a; /* Restore SP */ | |
break; | |
case RLEQ: | |
if ((cpu_n_flag() | cpu_z_flag()) == 1) { | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
} | |
break; | |
case RLEQU: | |
if ((cpu_c_flag() | cpu_z_flag()) == 1) { | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
} | |
break; | |
case RLSS: | |
if ((cpu_n_flag() == 1) & (cpu_z_flag() == 0)) { | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
} | |
break; | |
case REQL: | |
if (cpu_z_flag() == 1) { | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
} | |
break; | |
case REQLU: | |
if (cpu_z_flag() == 1) { | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
} | |
break; | |
case RSB: | |
R[NUM_PC] = cpu_pop_word(); | |
continue; | |
case SAVE: | |
/* Save the FP register */ | |
write_w(R[NUM_SP], R[NUM_FP]); | |
/* Save all the registers from the one identified by the | |
src operand up to FP (exclusive) */ | |
for (a = src1->reg, b = 4; a < NUM_FP; a++, b += 4) { | |
write_w(R[NUM_SP] + b, R[a]); | |
} | |
R[NUM_SP] = R[NUM_SP] + 28; | |
R[NUM_FP] = R[NUM_SP]; | |
break; | |
case STRCPY: | |
a = 0; | |
b = 0; | |
do { | |
b = read_b(R[0] + a, ACC_AF); | |
write_b(R[1] + a, (uint8) b); | |
a++; | |
} while (b != '\0'); | |
break; | |
case TSTW: | |
a = cpu_read_op(src1); | |
cpu_set_n_flag((int32)a < 0); | |
cpu_set_z_flag(a == 0); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag(0); | |
break; | |
case TSTH: | |
a = cpu_read_op(src1); | |
cpu_set_n_flag((int16)a < 0); | |
cpu_set_z_flag(a == 0); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag(0); | |
break; | |
case TSTB: | |
a = cpu_read_op(src1); | |
cpu_set_n_flag((int8)a < 0); | |
cpu_set_z_flag(a == 0); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag(0); | |
break; | |
case WAIT: | |
cpu_in_wait = TRUE; | |
break; | |
case XORW2: | |
case XORH2: | |
case XORB2: | |
a = (cpu_read_op(src1) ^ cpu_read_op(dst)); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
break; | |
case XORW3: | |
case XORH3: | |
case XORB3: | |
a = (cpu_read_op(src1) ^ cpu_read_op(src2)); | |
cpu_write_op(dst, a); | |
cpu_set_nz_flags(a, dst); | |
cpu_set_c_flag(0); | |
cpu_set_v_flag_op(a, dst); | |
break; | |
default: | |
stop_reason = STOP_OPCODE; | |
break; | |
}; | |
/* Increment the PC appropriately */ | |
R[NUM_PC] += cpu_ilen; | |
} | |
return stop_reason; | |
} | |
static SIM_INLINE void cpu_on_process_exception(uint8 isc) | |
{ | |
/* TODO: Handle */ | |
stop_reason = STOP_EX; | |
return; | |
} | |
static SIM_INLINE void cpu_on_reset_exception(uint8 isc) | |
{ | |
uint32 new_pcbp; | |
sim_debug(EXECUTE_MSG, &cpu_dev, | |
"[%08x] [cpu_on_reset_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", | |
R[NUM_PC], isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); | |
if (isc == EXTERNAL_RESET) { | |
R[NUM_PSW] &= ~(PSW_R_MASK); | |
} | |
cpu_km = TRUE; | |
mmu_disable(); | |
abort_context = C_RESET_SYSTEM_DATA; | |
new_pcbp = read_w(0x80, ACC_AF); | |
abort_context = C_RESET_NEW_PCB; | |
cpu_context_switch_2(new_pcbp); | |
cpu_km = FALSE; | |
abort_context = C_NONE; | |
} | |
static SIM_INLINE void cpu_on_stack_exception(uint8 isc) | |
{ | |
uint32 new_pcbp; | |
sim_debug(EXECUTE_MSG, &cpu_dev, | |
"[%08x] [cpu_on_stack_exception %d] SP=%08x PCBP=%08x ISP=%08x\n", | |
R[NUM_PC], isc, R[NUM_SP], R[NUM_PCBP], R[NUM_ISP]); | |
abort_context = C_RESET_SYSTEM_DATA; | |
cpu_km = TRUE; | |
new_pcbp = read_w(0x88, ACC_AF); | |
abort_context = C_RESET_INT_STACK; | |
irq_push_word(R[NUM_PCBP]); | |
abort_context = C_PROCESS_OLD_PCB; | |
R[NUM_PSW] &= ~(PSW_ET_MASK|PSW_ISC_MASK); | |
R[NUM_PSW] |= (2 << PSW_ET); | |
R[NUM_PSW] |= (uint32) (isc << PSW_ISC); | |
cpu_context_switch_1(new_pcbp); | |
cpu_context_switch_2(new_pcbp); | |
/* Set ISC, TM, and ET to 7, 0, 3 in new PSW */ | |
R[NUM_PSW] &= ~(PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); | |
R[NUM_PSW] |= (7 << PSW_ISC); | |
R[NUM_PSW] |= (3 << PSW_ET); | |
cpu_km = FALSE; | |
abort_context = C_NONE; | |
} | |
static SIM_INLINE void cpu_on_normal_exception(uint8 isc) | |
{ | |
sim_debug(EXECUTE_MSG, &cpu_dev, | |
"[%08x] [cpu_on_normal_exception %d] %%sp=%08x abort_context=%d\n", | |
R[NUM_PC], isc, R[NUM_SP], abort_context); | |
abort_context = C_PROCESS_GATE_PCB; | |
cpu_km = TRUE; | |
if (R[NUM_SP] < read_w(R[NUM_PCBP] + 12, ACC_AF) || | |
R[NUM_SP] >= read_w(R[NUM_PCBP] + 16, ACC_AF)) { | |
sim_debug(EXECUTE_MSG, &cpu_dev, | |
"[%08x] STACK OUT OF BOUNDS IN EXCEPTION HANDLER. " | |
"SP=%08x, R[NUM_PCBP]+12=%08x, " | |
"R[NUM_PCBP]+16=%08x\n", | |
R[NUM_PC], | |
R[NUM_SP], | |
read_w(R[NUM_PCBP] + 12, ACC_AF), | |
read_w(R[NUM_PCBP] + 16, ACC_AF)); | |
abort_context = C_NONE; | |
cpu_abort(STACK_EXCEPTION, STACK_BOUND); | |
} | |
cpu_km = FALSE; | |
/* Set context for STACK (FAULT) */ | |
abort_context = C_STACK_FAULT; | |
write_w(R[NUM_SP], R[NUM_PC]); | |
/* Write 0, 3 to TM, ET fields of PSW */ | |
R[NUM_PSW] &= ~(PSW_TM_MASK|PSW_ET_MASK); | |
R[NUM_PSW] |= (3 << PSW_ET); | |
/* Save address of next instruction and PSW to stack */ | |
write_w(R[NUM_SP] + 4, R[NUM_PSW]); | |
/* Set context for RESET (GATE VECTOR) */ | |
abort_context = C_RESET_GATE_VECTOR; | |
cpu_perform_gate(0, ((uint32) isc) << 3); | |
/* Finish push of old PC and PSW */ | |
R[NUM_SP] += 8; | |
abort_context = C_NONE; | |
} | |
static SIM_INLINE void cpu_perform_gate(uint32 index1, uint32 index2) | |
{ | |
uint32 gate_l2, new_psw; | |
cpu_km = TRUE; | |
gate_l2 = read_w(index1, ACC_AF) + index2; | |
/* Get new PSW from second-level table */ | |
new_psw = read_w(gate_l2, ACC_AF); | |
/* Clear state in PSW */ | |
new_psw &= ~(PSW_PM_MASK|PSW_IPL_MASK|PSW_R_MASK| | |
PSW_ISC_MASK|PSW_TM_MASK|PSW_ET_MASK); | |
/* Set PM in new PSW */ | |
new_psw |= (R[NUM_PSW] & PSW_CM_MASK) >> 2; /* PM */ | |
new_psw |= (R[NUM_PSW] & PSW_IPL_MASK); /* IPL */ | |
new_psw |= (R[NUM_PSW] & PSW_R_MASK); /* R */ | |
/* Set new PSW ISC, TM, and ET to 7, 1, 3 */ | |
new_psw |= (7 << PSW_ISC); /* ISC */ | |
new_psw |= (1 << PSW_TM); /* TM */ | |
new_psw |= (3 << PSW_ET); /* ET */ | |
R[NUM_PC] = read_w(gate_l2 + 4, ACC_AF); | |
R[NUM_PSW] = new_psw; | |
cpu_km = FALSE; | |
} | |
/* | |
* TODO: Setting 'data' to the effective address is bogus. We're only | |
* doing it because we want to get the address when we trace the | |
* instructions using "SHOW CPU HISTORY". We should just put | |
* effective_address as a field in the operand struct and make | |
* cpu_show_hist smarter. | |
*/ | |
static uint32 cpu_effective_address(operand *op) | |
{ | |
/* Register Deferred */ | |
if (op->mode == 5 && op->reg != 11) { | |
return R[op->reg]; | |
} | |
/* Absolute */ | |
if (op->mode == 7 && op->reg == 15) { | |
return op->embedded.w; | |
} | |
/* Absolute Deferred */ | |
if (op->mode == 14 && op->reg == 15) { | |
/* May cause exception */ | |
return read_w(op->embedded.w, ACC_AF); | |
} | |
/* FP Short Offset */ | |
if (op->mode == 6 && op->reg != 15) { | |
return R[NUM_FP] + sign_extend_b(op->embedded.b); | |
} | |
/* AP Short Offset */ | |
if (op->mode == 7 && op->reg != 15) { | |
return R[NUM_AP] + sign_extend_b(op->embedded.b); | |
} | |
/* Word Displacement */ | |
if (op->mode == 8) { | |
return R[op->reg] + op->embedded.w; | |
} | |
/* Word Displacement Deferred */ | |
if (op->mode == 9) { | |
return read_w(R[op->reg] + op->embedded.w, ACC_AF); | |
} | |
/* Halfword Displacement */ | |
if (op->mode == 10) { | |
return R[op->reg] + sign_extend_h(op->embedded.h); | |
} | |
/* Halfword Displacement Deferred */ | |
if (op->mode == 11) { | |
return read_w(R[op->reg] + sign_extend_h(op->embedded.h), ACC_AF); | |
} | |
/* Byte Displacement */ | |
if (op->mode == 12) { | |
return R[op->reg] + sign_extend_b(op->embedded.b); | |
} | |
/* Byte Displacement Deferred */ | |
if (op->mode == 13) { | |
return read_w(R[op->reg] + sign_extend_b(op->embedded.b), ACC_AF); | |
} | |
assert(0); | |
return 0; | |
} | |
/* | |
* Read and Write routines for operands. | |
* | |
* The rules for dealing with the type (signed/unsigned, | |
* byte/halfword/word) of operands are fairly complex. | |
* | |
* 1. The expanded operand mode does not affect the treatment of | |
* Literal Mode operands. All literals are signed. | |
* | |
* 2. The expanded operand mode does not affect the length of | |
* Immediate Mode operands, but does affect whether they are signed | |
* or unsigned. | |
* | |
* 3. When using expanded-mode operands, the new type remains in | |
* effect for the operands that folow in the instruction unless | |
* another expanded operand mode overrides it. (This rule in | |
* particular is managed by decode_instruction()) | |
* | |
* 4. The expanded operand mode is illegal with coprocessor instructions | |
* and CALL, SAVE, RESTORE, SWAP INTERLOCKED, PUSAHW, PUSHAW, POPW, | |
* and JSB. (Illegal Operand Fault) | |
* | |
* 5. When writing a byte, the Negative (N) flag is set based on the | |
* high bit of the data type being written, regardless of the SIGN | |
* of the extended datatype. e.g.: {ubyte} and {sbyte} both check | |
* for bit 7, {uhalf} and {shalf} both check for bit 15, and | |
* {uword} and {sword} both check for bit 31. | |
* | |
* 6. For instructions with a signed destination, V is set if the sign | |
* bit of the output value is different from any truncated bit of | |
* the result. For instructions with an unsigned destination, V is | |
* set if any truncated bit is 1. | |
*/ | |
/* | |
* Read the data referenced by an operand. Performs sign or zero | |
* extension as required by the read width and operand type, then | |
* returns the read value. | |
* | |
* "All operations are performed only on 32-bit quantities even though | |
* an instruction may specify a byte or halfword operand. The WE | |
* 32100 Microprocessor reads in the correct number of bits for the | |
* operand and extends the data automatically to 32 bits. It uses | |
* sign extension when reading signed data or halfwords and zero | |
* extension when reading unsigned data or bytes (or bit fields that | |
* contain less than 32 bits). The data type of the source operand | |
* determines how many bits are fetched and what type of extension is | |
* applied. Bytes are treated as unsigned, while halfwords and words | |
* are considered signed. The type of extension applied can be | |
* changed using the expanded-operand type mode as described in 3.4.5 | |
* Expanded-Operand Type Mode. For sign extension, the value of the | |
* MSB or sign bit of the data fills the high-order bits to form a | |
* 32-bit value. In zero extension, zeros fill the high order bits. | |
* The microprocessor automatically extends a byte or halfword to 32 | |
* bits before performing an operation. Figure 3-3 illustrates sign | |
* and zero extension. An arithmetic, logical, data transfer, or bit | |
* field operation always yields an intermediate result that is 32 | |
* bits in length. If the result is to be stored in a register, the | |
* processor writes all 32 bits to that register. The processor | |
* automatically strips any surplus high-order bits from a result | |
* when writing bytes or halfwords to memory." -- "WE 32100 | |
* Microprocessor Information Manual", Section 3.1.1 | |
* | |
*/ | |
static uint32 cpu_read_op(operand * op) | |
{ | |
uint32 eff; | |
uint32 data; | |
/* Register */ | |
if (op->mode == 4 && op->reg < 15) { | |
switch (op_type(op)) { | |
case WD: | |
case UW: | |
data = R[op->reg]; | |
break; | |
case HW: | |
data = sign_extend_h(R[op->reg] & HALF_MASK); | |
break; | |
case UH: | |
data = R[op->reg] & HALF_MASK; | |
break; | |
case BT: | |
data = R[op->reg] & BYTE_MASK; | |
break; | |
case SB: | |
data = sign_extend_b(R[op->reg] & BYTE_MASK); | |
break; | |
default: | |
assert(0); | |
} | |
op->data = data; | |
return data; | |
} | |
/* Literal */ | |
if (op->mode < 4 || op->mode == 15) { | |
/* Both positive and negative literals are _always_ treated as | |
signed bytes, and they are _always_ sign extended. They | |
simply ignore expanded datatypes. */ | |
data = sign_extend_b(op->embedded.b); | |
op->data = data; | |
return data; | |
} | |
/* Immediate */ | |
if (op->reg == 15 && | |
(op->mode == 4 || op->mode == 5 || op->mode == 6)) { | |
switch (op->mode) { | |
case 4: /* Word Immediate */ | |
data = op->embedded.w; | |
op->data = data; | |
return data; | |
case 5: /* Halfword Immediate */ | |
data = sign_extend_h(op->embedded.h); | |
op->data = data; | |
return data; | |
case 6: /* Byte Immedaite */ | |
data = sign_extend_b(op->embedded.b); | |
op->data = data; | |
return data; | |
} | |
} | |
/* At this point, we'll need to find an effective address */ | |
eff = cpu_effective_address(op); | |
switch (op_type(op)) { | |
case WD: /* Signed Word */ | |
case UW: /* Unsigned Word */ | |
data = read_w(eff, ACC_OF); | |
op->data = data; | |
return data; | |
case HW: /* Signed Halfword */ | |
data = sign_extend_h(read_h(eff, ACC_OF)); | |
op->data = data; | |
return data; | |
case UH: /* Unsigned Halfword */ | |
data = read_h(eff, ACC_OF); | |
op->data = data; | |
return data; | |
case SB: /* Signed Byte */ | |
data = sign_extend_b(read_b(eff, ACC_OF)); | |
op->data = data; | |
return data; | |
case BT: /* Unsigned Byte */ | |
data = read_b(eff, ACC_OF); | |
op->data = data; | |
return data; | |
default: | |
assert(0); | |
return 0; | |
} | |
} | |
static void cpu_write_op(operand * op, t_uint64 val) | |
{ | |
uint32 eff; | |
op->data = (uint32) val; | |
/* Writing to a register. */ | |
if (op->mode == 4 && op->reg < 15) { | |
if ((op->reg == NUM_PSW || op->reg == NUM_PCBP || op->reg == NUM_ISP) && | |
cpu_execution_level() != EX_LVL_KERN) { | |
cpu_abort(NORMAL_EXCEPTION, PRIVILEGED_REGISTER); | |
return; | |
} | |
/* Registers always get the full 32-bits written */ | |
R[op->reg] = (uint32) val; | |
return; | |
} | |
/* Literal mode is not legal. */ | |
if (op->mode < 4 || op->mode == 15) { | |
cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); | |
return; | |
} | |
/* Immediate mode is not legal. */ | |
if (op->reg == 15 && | |
(op->mode == 4 || op->mode == 5 || op->mode == 6)) { | |
cpu_abort(NORMAL_EXCEPTION, INVALID_DESCRIPTOR); | |
return; | |
} | |
eff = cpu_effective_address(op); | |
switch (op_type(op)) { | |
case UW: | |
case WD: | |
write_w(eff, (uint32) val); | |
break; | |
case HW: | |
case UH: | |
if (val > HALF_MASK) { | |
cpu_set_v_flag(TRUE); | |
} | |
write_h(eff, val & HALF_MASK); | |
break; | |
case SB: | |
case BT: | |
if (val > BYTE_MASK) { | |
cpu_set_v_flag(TRUE); | |
} | |
write_b(eff, val & BYTE_MASK); | |
break; | |
default: | |
assert(0); | |
} | |
} | |
/* | |
* This returns the current state of the IPL (Interrupt | |
* Priority Level) bus. This is affected by: | |
* | |
* - Latched values in the CSR for: | |
* o CSRCLK 15 | |
* o CSRDMA 13 | |
* o CSRUART 13 | |
* o CSRDISK 11 | |
* o CSRPIR9 9 | |
* o CSRPIR8 8 | |
* - IRQ currently enabled for: | |
* o HD Ctlr. 11 | |
*/ | |
static SIM_INLINE uint8 cpu_ipl() | |
{ | |
/* CSRPIR9 is cleared by writing to c_pir8 */ | |
if (csr_data & CSRPIR8) { | |
return 8; | |
} | |
/* CSRPIR9 is cleared by writing to c_pir9 */ | |
if (csr_data & CSRPIR9) { | |
return 9; | |
} | |
/* CSRDISK is cleared when the floppy "if_irq" goes low */ | |
if (id_irq || (csr_data & CSRDISK)) { | |
return 11; | |
} | |
/* CSRDMA is cleared by write/read to 0x49011 */ | |
/* CSRUART is cleared when the uart "iu_irq" goes low */ | |
if ((csr_data & CSRUART) || (csr_data & CSRDMA)) { | |
return 13; | |
} | |
/* CSRCLK is cleared by $clrclkint */ | |
if (csr_data & CSRCLK) { | |
return 15; | |
} | |
return 0; | |
} | |
/* | |
* Returns the correct datatype for an operand -- either extended type | |
* or default type. | |
*/ | |
static SIM_INLINE int8 op_type(operand *op) { | |
if (op->etype > -1) { | |
return op->etype; | |
} else { | |
return op->dtype; | |
} | |
} | |
static SIM_INLINE t_bool op_signed(operand *op) { | |
return (op_type(op) == WD || op_type(op) == HW || op_type(op) == SB); | |
} | |
static SIM_INLINE t_bool is_byte_immediate(operand * oper) | |
{ | |
return oper->mode == 6 && oper->reg == 15; | |
} | |
static SIM_INLINE t_bool is_halfword_immediate(operand * oper) | |
{ | |
return oper->mode == 5 && oper->reg == 15; | |
} | |
static SIM_INLINE t_bool is_word_immediate(operand * oper) | |
{ | |
return oper->mode == 4 && oper->reg == 15; | |
} | |
static SIM_INLINE t_bool is_positive_literal(operand * oper) | |
{ | |
return (oper->mode == 0 || | |
oper->mode == 1 || | |
oper->mode == 2); | |
} | |
static SIM_INLINE t_bool is_negative_literal(operand * oper) | |
{ | |
return oper->mode == 15; | |
} | |
/* Returns true if the operand may not be used as a destination */ | |
static SIM_INLINE t_bool invalid_destination(operand * oper) | |
{ | |
return (is_byte_immediate(oper) || | |
is_halfword_immediate(oper) || | |
is_word_immediate(oper) || | |
is_positive_literal(oper) || | |
is_negative_literal(oper)); | |
} | |
static SIM_INLINE uint32 sign_extend_b(uint8 val) | |
{ | |
if (val & 0x80) | |
return ((uint32) val) | 0xffffff00; | |
return (uint32) val; | |
} | |
static SIM_INLINE uint32 zero_extend_b(uint8 val) | |
{ | |
return (uint32) val & BYTE_MASK; | |
} | |
static SIM_INLINE uint32 sign_extend_h(uint16 val) | |
{ | |
if (val & 0x8000) | |
return ((uint32) val) | 0xffff0000; | |
return (uint32) val; | |
} | |
static SIM_INLINE uint32 zero_extend_h(uint16 val) | |
{ | |
return (uint32) val & HALF_MASK; | |
} | |
/* | |
* Returns the current CPU execution level. | |
*/ | |
static SIM_INLINE uint8 cpu_execution_level() | |
{ | |
return (R[NUM_PSW] & PSW_CM_MASK) >> PSW_CM; | |
} | |
static SIM_INLINE t_bool cpu_z_flag() | |
{ | |
return (R[NUM_PSW] & PSW_Z_MASK) != 0; | |
} | |
static SIM_INLINE t_bool cpu_n_flag() | |
{ | |
return (R[NUM_PSW] & PSW_N_MASK) != 0; | |
} | |
static SIM_INLINE t_bool cpu_c_flag() | |
{ | |
return (R[NUM_PSW] & PSW_C_MASK) != 0; | |
} | |
static SIM_INLINE t_bool cpu_v_flag() | |
{ | |
return (R[NUM_PSW] & PSW_V_MASK) != 0; | |
} | |
static SIM_INLINE void cpu_set_z_flag(t_bool val) | |
{ | |
if (val) { | |
R[NUM_PSW] = R[NUM_PSW] | PSW_Z_MASK; | |
} else { | |
R[NUM_PSW] = R[NUM_PSW] & ~PSW_Z_MASK; | |
} | |
} | |
static SIM_INLINE void cpu_set_n_flag(t_bool val) | |
{ | |
if (val) { | |
R[NUM_PSW] = R[NUM_PSW] | PSW_N_MASK; | |
} else { | |
R[NUM_PSW] = R[NUM_PSW] & ~PSW_N_MASK; | |
} | |
} | |
static SIM_INLINE void cpu_set_c_flag(t_bool val) | |
{ | |
if (val) { | |
R[NUM_PSW] = R[NUM_PSW] | PSW_C_MASK; | |
} else { | |
R[NUM_PSW] = R[NUM_PSW] & ~PSW_C_MASK; | |
} | |
} | |
static SIM_INLINE void cpu_set_v_flag_op(t_uint64 val, operand *op) | |
{ | |
switch(op_type(op)) { | |
case WD: | |
case UW: | |
cpu_set_v_flag(0); | |
break; | |
case HW: | |
case UH: | |
cpu_set_v_flag(val > HALF_MASK); | |
break; | |
case BT: | |
case SB: | |
default: | |
cpu_set_v_flag(val > BYTE_MASK); | |
break; | |
} | |
} | |
static SIM_INLINE void cpu_set_v_flag(t_bool val) | |
{ | |
if (val) { | |
R[NUM_PSW] = R[NUM_PSW] | PSW_V_MASK; | |
if (R[NUM_PSW] & PSW_OE_MASK) { | |
cpu_abort(NORMAL_EXCEPTION, INTEGER_OVERFLOW); | |
} | |
} else { | |
R[NUM_PSW] = R[NUM_PSW] & ~PSW_V_MASK; | |
} | |
} | |
static void cpu_set_nz_flags(t_uint64 data, operand *dst) | |
{ | |
int8 type = op_type(dst); | |
switch (type) { | |
case WD: | |
case UW: | |
cpu_set_n_flag(!!(WD_MSB & data)); | |
cpu_set_z_flag((data & WORD_MASK) == 0); | |
break; | |
case HW: | |
case UH: | |
cpu_set_n_flag(HW_MSB & data); | |
cpu_set_z_flag((data & HALF_MASK) == 0); | |
break; | |
case BT: | |
case SB: | |
cpu_set_n_flag(BT_MSB & data); | |
cpu_set_z_flag((data & BYTE_MASK) == 0); | |
break; | |
} | |
} | |
static SIM_INLINE void cpu_push_word(uint32 val) | |
{ | |
write_w(R[NUM_SP], val); | |
R[NUM_SP] += 4; | |
} | |
static SIM_INLINE uint32 cpu_pop_word() | |
{ | |
uint32 result; | |
/* We always read fromthe stack first BEFORE decrementing, | |
in case this causes a fault. */ | |
result = read_w(R[NUM_SP] - 4, ACC_AF); | |
R[NUM_SP] -= 4; | |
return result; | |
} | |
static SIM_INLINE void irq_push_word(uint32 val) | |
{ | |
write_w(R[NUM_ISP], val); | |
R[NUM_ISP] += 4; | |
} | |
static SIM_INLINE uint32 irq_pop_word() | |
{ | |
R[NUM_ISP] -= 4; | |
return read_w(R[NUM_ISP], ACC_AF); | |
} | |
static SIM_INLINE t_bool op_is_psw(operand *op) | |
{ | |
return (op->mode == 4 && op->reg == NUM_PSW); | |
} | |
static SIM_INLINE t_bool op_is_sp(operand *op) | |
{ | |
return op->reg == NUM_SP; | |
} | |
static SIM_INLINE void sub(t_uint64 a, t_uint64 b, operand *dst) | |
{ | |
t_uint64 result; | |
result = a - b; | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
cpu_set_c_flag((uint32)b > (uint32)a); | |
cpu_set_v_flag_op(result, dst); | |
} | |
static SIM_INLINE void add(t_uint64 a, t_uint64 b, operand *dst) | |
{ | |
t_uint64 result; | |
result = a + b; | |
cpu_write_op(dst, result); | |
cpu_set_nz_flags(result, dst); | |
switch(op_type(dst)) { | |
case WD: | |
case UW: | |
cpu_set_c_flag(result > WORD_MASK); | |
cpu_set_v_flag(!! (((a ^ ~b) & (a ^ result)) & WD_MSB)); | |
break; | |
case HW: | |
case UH: | |
cpu_set_c_flag(result > HALF_MASK); | |
cpu_set_v_flag(((a ^ ~b) & (a ^ result)) & HW_MSB); | |
break; | |
case BT: | |
case SB: | |
cpu_set_c_flag(result > BYTE_MASK); | |
cpu_set_v_flag(((a ^ ~b) & (a ^ result)) & BT_MSB); | |
} | |
} | |
/* | |
* Set PSW's ET and ISC fields, and store global exception or fault | |
* state appropriately. | |
*/ | |
void cpu_abort(uint8 et, uint8 isc) | |
{ | |
/* We don't trap Integer Overflow if the OE bit is not set */ | |
if ((R[NUM_PSW] & PSW_OE_MASK) || isc != INTEGER_OVERFLOW) { | |
R[NUM_PSW] &= ~(PSW_ISC_MASK); /* Clear ISC */ | |
R[NUM_PSW] &= ~(PSW_ET_MASK); /* Clear ET */ | |
R[NUM_PSW] |= et; /* Set ET */ | |
R[NUM_PSW] |= (uint32) (isc << PSW_ISC); /* Set ISC */ | |
if (et == 3 && (isc == BREAKPOINT_TRAP || | |
isc == INTEGER_OVERFLOW || | |
isc == TRACE_TRAP)) { | |
longjmp(save_env, ABORT_TRAP); | |
} else { | |
longjmp(save_env, ABORT_EXC); | |
} | |
} | |
} |