Mike Chambers
Veteran Member
- Joined
- Sep 2, 2006
- Messages
- 2,621
well, this is going to be a long post... here's my implementation of the 8086 (actually 80186) - i know you're not looking to emulate one, but the major ideas demonstrated in this apply to all CPUs.
first, you are going to want to take a look at this document i compiled when i started working on this. it describes the general prefixes/opcode format. it also explains in detail the mode/reg/rm addressing mode byte which is dealt with in some of the code: http://rubbermallet.org/8086 notes.pdf
first the "cpu.h" header file:
and "cpu.c" now... declaring some important stuff at the top.
this function is called on CPU power-up/reset:
routines to calculate flag states used after most instructions are shown next. i should point out that the flag_log8 and flag_log16 are the flag calculation functions called for following bitwise logical operations: AND, OR, XOR. they calculate only sign, zero, and parity flag values. carry and overflow flags are always cleared by them, as it's not possible for bitwise logic ops to cause a carry/overflow.
next, since most instructions are just the same thing using different addressing schemes or registers we have some general routines for the common instructions. oper1/oper2 (16-bit), or oper1b/oper2b (8-bit) are set before calling these where the first is the destination operand and the second is the source operand. the result is stuck into a 16-bit or 8-bit result variable which is used to store the result after these return.
next up, we of course need some functions to read/write memory. the functions to emulate stack pushes and pops are also here.
this next stuff is very important, the 8086 uses an addressing mode byte in most instructions. it contains three fields. the addressing mode (values 0 to 3), register field (0 to 7), and register/memory field (0 to 7)
the mode field indicates the meaning of the register/memory field. refer to the PDF i linked at the beginning of the post. also note that several instructions (called the "GRP" or group instructions) have an addressing mode byte, but the "register" field is actually used as an extension to determine the actual operation to be performed. yes, x86 is freakin' weird.
below are the functions that parse the addressing mode byte, and another one which is used to calculate the effective memory address based on the fields in that byte.
the next functions read and write data from registers or memory depending on the meaning of the "register/memory" field in the addr mode byte:
the next instruction is very important, it handles interrupt calls by looking up the entry in the IVT at 0000:0000, pushes the flags, current CS and IP to the stack and then jumps to the handler described by the vector. there are a couple things it checks for first because my PC emu needs to handle some things on a higher level rather than let the actual BIOS ROM handle, like disk accesses on int 13h, etc..
this next huge chunk is a big bucket of fun. most of the GRP opcodes have seperate functions here that check the register field in the addressing mode byte to determine the actual operation that should be performed. lots of stuff here, like GRP2 which is bit shifing, others handle NEG, NOT, MUL, DIV, etc.
the rest is too much, it won't let me paste in a single post.
first, you are going to want to take a look at this document i compiled when i started working on this. it describes the general prefixes/opcode format. it also explains in detail the mode/reg/rm addressing mode byte which is dealt with in some of the code: http://rubbermallet.org/8086 notes.pdf
first the "cpu.h" header file:
Code:
#define regax 0
#define regcx 1
#define regdx 2
#define regbx 3
#define regsp 4
#define regbp 5
#define regsi 6
#define regdi 7
#define reges 0
#define regcs 1
#define regss 2
#define regds 3
#ifdef __BIG_ENDIAN__
#define regal 1
#define regah 0
#define regcl 3
#define regch 2
#define regdl 5
#define regdh 4
#define regbl 7
#define regbh 6
#else
#define regal 0
#define regah 1
#define regcl 2
#define regch 3
#define regdl 4
#define regdh 5
#define regbl 6
#define regbh 7
#endif
union _bytewordregs_ {
uint16_t wordregs[8];
uint8_t byteregs[8];
};
#define StepIP(x) ip+=x
#define getmem8(x,y) read86(segbase(x)+y)
#define getmem16(x,y) (read86(segbase(x)+y) | ((uint16_t)read86(segbase(x)+y+1)<<8))
#define putmem8(x,y,z) write86(segbase(x)+y, z)
#define putmem16(x,y,z) write86(segbase(x)+y, (z)&0xFF); write86(segbase(x)+y+1, (z)>>8)
#define signext(value) ((((uint16_t)value&0x80)*0x1FE)|(uint16_t)value)
#define signext32(value) ((((uint32_t)value&0x8000)*0x1FFFE)|(uint32_t)value)
#define getreg16(regid) regs.wordregs[regid]
#define getreg8(regid) regs.byteregs[byteregtable[regid]]
#define putreg16(regid, writeval) regs.wordregs[regid] = writeval
#define putreg8(regid, writeval) regs.byteregs[byteregtable[regid]] = writeval
#define getsegreg(regid) segregs[regid]
#define putsegreg(regid, writeval) segregs[regid] = writeval
#define segbase(x) ((uint32_t)x<<4)
and "cpu.c" now... declaring some important stuff at the top.
Code:
#include "cpu.h"
uint16_t segregs[4];
uint8_t opcode = 0, segoverride = 0, reptype = 0;
uint16_t savecs = 0, saveip = 0, ip = 0, useseg = 0, oldsp = 0;
uint8_t tempcf, oldcf, cf, pf, af, zf, sf, tf, ifl, df, of; //stuff for CPU flags
uint8_t mode, reg, rm; //data from adressing mode bytes
uint16_t oper1 = 0, oper2 = 0, res16 = 0, disp16 = 0, temp16 = 0, dummy = 0, stacksize = 0, frametemp = 0;
uint8_t oper1b = 0, oper2b = 0, res8 = 0, disp8 = 0, temp8 = 0, nestlev = 0, addrbyte = 0;
uint32_t temp1 = 0, temp2 = 0, temp3 = 0, temp4 = 0, temp5 = 0, temp32 = 0, tempaddr32 = 0, ea = 0;
int32_t result = 0;
uint64_t totalexec = 0; //keep track of total instructions executed
union _bytewordregs_ regs; //general registers are in a union of uint16 and uint8's
//for example, AX and AH/AL share memory space:
// | AX |
// | AL | AH |
//next is a lookup table for the "register" field in the 8-bit register addressing mode
static const uint8_t byteregtable[8] = { regal, regcl, regdl, regbl, regah, regch, regdh, regbh };
#define makeflagsword() (2 | (uint16_t)cf | ((uint16_t)pf << 2) | ((uint16_t)af << 4) | ((uint16_t)zf << 6) \
| ((uint16_t)sf << 7) | ((uint16_t)tf << 8) | ((uint16_t)ifl << 9) | ((uint16_t)df << 10) | ((uint16_t)of << 11))
#define decodeflagsword(x) {\
temp16 = x;\
cf = temp16 & 1;\
pf = (temp16 >> 2) & 1;\
af = (temp16 >> 4) & 1;\
zf = (temp16 >> 6) & 1;\
sf = (temp16 >> 7) & 1;\
tf = (temp16 >> 8) & 1;\
ifl = (temp16 >> 9) & 1;\
df = (temp16 >> 10) & 1;\
of = (temp16 >> 11) & 1;\
}
uint32_t framenum = 0;
extern const char *build;
uint16_t newoffs = 0;
uint8_t interrupt_ok = 0, savesp;
extern uint8_t dopktrecv, pktokay;
extern uint16_t rcvseg, rcvoff, hdrlen, handpkt;
void intcall86(uint8_t intnum);
extern struct struct_drive {
FILE *diskfile;
uint32_t filesize;
uint16_t cyls;
uint16_t sects;
uint16_t heads;
uint8_t inserted;
} disk[256];
extern uint8_t readVGA(uint32_t addr32);
extern void writeVGA(uint32_t addr32, uint8_t value);
extern void vidinterrupt();
extern void diskhandler();
extern void readdisk(uint8_t drivenum, uint16_t dstseg, uint16_t dstoff, uint16_t cyl, uint16_t sect, uint16_t head, uint16_t sectcount);
extern void doirq(uint8_t irqnum);
extern uint8_t nextintr();
extern void portout(uint16_t portnum, uint16_t value);
extern uint16_t portin(uint16_t portnum);
this function is called on CPU power-up/reset:
Code:
void reset86() {
uint16_t i, cnt, bitcount;
segregs[regcs] = 0xFFFF; //upon power-up or reset, the CPU's program counter starts at FFFF:0000
ip = 0x0000;
//generate parity lookup table
for (i=0; i<256; i++) {
bitcount = 0;
for (cnt=0; cnt<8; cnt++)
bitcount += ((i >> cnt) & 1);
if (bitcount & 1) parity[i] = 0; else parity[i] = 1;
}
}
routines to calculate flag states used after most instructions are shown next. i should point out that the flag_log8 and flag_log16 are the flag calculation functions called for following bitwise logical operations: AND, OR, XOR. they calculate only sign, zero, and parity flag values. carry and overflow flags are always cleared by them, as it's not possible for bitwise logic ops to cause a carry/overflow.
Code:
void flag_szp8(uint8_t value) {
if (!value) zf = 1; else zf = 0;
if (value & 0x80) sf = 1; else sf = 0;
pf = parity[value];
}
void flag_szp16(uint16_t value) {
if (!value) zf = 1; else zf = 0;
if (value & 0x8000) sf = 1; else sf = 0;
pf = parity[value & 255];
}
void flag_log8(uint8_t value) {
flag_szp8(value);
cf = 0; of = 0;
}
void flag_log16(uint16_t value) {
flag_szp16(value);
cf = 0; of = 0;
}
void flag_adc8(uint8_t v1, uint8_t v2, uint8_t v3) { //v1 = dest operand, v2 = source operand, v3 = carry flag
uint16_t dst;
dst = (uint16_t)v1 + (uint16_t)v2 + (uint16_t)v3;
flag_szp8((uint8_t)dst); //go set/clear sign, zero, and parity flags
if (((dst ^ v1) & (dst ^ v2) & 0x80L) == 0x80L) of = 1; else of = 0; //set or clear overflow flag
if (dst & 0xFF00L) cf = 1; else cf = 0; //set or clear carry flag
if (((v1 ^ v2 ^ dst) & 0x10L) == 0x10L) af = 1; else af = 0; //set or clear auxiliary flag
}
void flag_adc16(uint16_t v1, uint16_t v2, uint16_t v3) {
uint32_t dst;
dst = (uint32_t)v1 + (uint32_t)v2 + (uint32_t)v3;
flag_szp16((uint16_t)dst);
if ((((dst ^ v1) & (dst ^ v2)) & 0x8000L) == 0x8000L) of = 1; else of = 0;
if (dst & 0xFFFF0000L) cf = 1; else cf = 0;
if (((v1 ^ v2 ^ dst) & 0x10L) == 0x10L) af = 1; else af = 0;
}
void flag_add8(uint8_t v1, uint8_t v2) {
uint16_t dst;
dst = (uint16_t)v1 + (uint16_t)v2;
flag_szp8((uint8_t)dst);
if (dst & 0xFF00L) cf = 1; else cf = 0;
if (((dst ^ v1) & (dst ^ v2) & 0x80L) == 0x80L) of = 1; else of = 0;
if (((v1 ^ v2 ^ dst) & 0x10L) == 0x10L) af = 1; else af = 0;
}
void flag_add16(uint16_t v1, uint16_t v2) {
uint32_t dst;
dst = (uint32_t)v1 + (uint32_t)v2;
flag_szp16((uint16_t)dst);
if (dst & 0xFFFF0000L) cf = 1; else cf = 0;
if (((dst ^ v1) & (dst ^ v2) & 0x8000L) == 0x8000L) of = 1; else of = 0;
if (((v1 ^ v2 ^ dst) & 0x10L) == 0x10L) af = 1; else af = 0;
}
void flag_sbb8(uint8_t v1, uint8_t v2, uint8_t v3) {
uint16_t dst;
v2 += v3;
dst = (uint16_t)v1 - (uint16_t)v2;
flag_szp8(dst & 0xFFL);
if (dst & 0xFF00L) cf = 1; else cf = 0;
if ((dst ^ v1) & (v1 ^ v2) & 0x80L) of = 1; else of = 0;
if ((v1 ^ v2 ^ dst) & 0x10L) af = 1; else af = 0;
}
void flag_sbb16(uint16_t v1, uint16_t v2, uint16_t v3) {
uint32_t dst;
v2 += v3;
dst = (uint32_t)v1 - (uint32_t)v2;
flag_szp16(dst & 0xFFFFL);
if (dst & 0xFFFF0000L) cf = 1; else cf = 0;
if ((dst ^ v1) & (v1 ^ v2) & 0x8000L) of = 1; else of = 0;
if ((v1 ^ v2 ^ dst) & 0x10L) af = 1; else af = 0;
}
void flag_sub8(uint8_t v1, uint8_t v2) {
uint16_t dst;
dst = (uint16_t)v1 - (uint16_t)v2;
flag_szp8(dst & 0xFFL);
if (dst & 0xFF00L) cf = 1; else cf = 0;
if ((dst ^ v1) & (v1 ^ v2) & 0x80L) of = 1; else of = 0;
if ((v1 ^ v2 ^ dst) & 0x10L) af = 1; else af = 0;
}
void flag_sub16(uint16_t v1, uint16_t v2) {
uint32_t dst;
dst = (uint32_t)v1 - (uint32_t)v2;
flag_szp16(dst & 0xFFFFL);
if (dst & 0xFFFF0000L) cf = 1; else cf = 0;
if ((dst ^ v1) & (v1 ^ v2) & 0x8000L) of = 1; else of = 0;
if ((v1 ^ v2 ^ dst) & 0x10L) af = 1; else af = 0;
}
next, since most instructions are just the same thing using different addressing schemes or registers we have some general routines for the common instructions. oper1/oper2 (16-bit), or oper1b/oper2b (8-bit) are set before calling these where the first is the destination operand and the second is the source operand. the result is stuck into a 16-bit or 8-bit result variable which is used to store the result after these return.
Code:
#define op_adc8() {\
res8 = oper1b + oper2b + cf;\
flag_adc8(oper1b, oper2b, cf);\
}
#define op_adc16() {\
res16 = oper1 + oper2 + cf;\
flag_adc16(oper1, oper2, cf);\
}
#define op_add8() {\
res8 = oper1b + oper2b;\
flag_add8(oper1b, oper2b);\
}
#define op_add16() {\
res16 = oper1 + oper2;\
flag_add16(oper1, oper2);\
}
#define op_and8() {\
res8 = oper1b & oper2b;\
flag_log8(res8);\
}
#define op_and16() {\
res16 = oper1 & oper2;\
flag_log16(res16);\
}
#define op_or8() {\
res8 = oper1b | oper2b;\
flag_log8(res8);\
}
#define op_or16() {\
res16 = oper1 | oper2;\
flag_log16(res16);\
}
#define op_xor8() {\
res8 = oper1b ^ oper2b;\
flag_log8(res8);\
}
#define op_xor16() {\
res16 = oper1 ^ oper2;\
flag_log16(res16);\
}
#define op_sub8() {\
res8 = oper1b - oper2b;\
flag_sub8(oper1b, oper2b);\
}
#define op_sub16() {\
res16 = oper1 - oper2;\
flag_sub16(oper1, oper2);\
}
#define op_sbb8() {\
res8 = oper1b - (oper2b + cf);\
flag_sbb8(oper1b, oper2b, cf);\
}
#define op_sbb16() {\
res16 = oper1 - (oper2 + cf);\
flag_sbb16(oper1, oper2, cf);\
}
next up, we of course need some functions to read/write memory. the functions to emulate stack pushes and pops are also here.
Code:
uint8_t read86(uint32_t addr32) {
addr32 &= 0xFFFFF; //just to be safe, AND-mask addr32 to 20 bits
if ((addr32>=0xA0000) && (addr32<=0xBFFFF)) { //are we reading from video RAM?
if ((vidmode!=0x13) && (vidmode!=0x12) && (vidmode!=0xD)) return(RAM[addr32]); //is our video state a VGA mode? if not, read from regular RAM.
else return(readVGA(addr32-0xA0000)); //if it's a VGA mode, send to VGA memory handler for processing (for unchained modes, etc)
}
switch (addr32) {
case 0x410: //0040:0010 is the equipment word, by forcing a value here we simulate the DIP switch on a PC or XT motherboard
return(0x41); //report CGA video (0x41 is VGA/EGA, 0x61 is CGA, 0x31 = MDA)
case 0x475: //hard drive count
return(hdcount);
default:
return(RAM[addr32]);
}
}
void write86(uint32_t addr32, uint8_t value) {
tempaddr32 = addr32 & 0xFFFFF; //just to be safe, AND-mask addr32 to 20 bits
if (readonly[tempaddr32]) return; //if requested address is not allowed to be written to, bail out
if ((tempaddr32>=0xA0000) && (tempaddr32<=0xBFFFF)) { //are we writing to video RAM?
if ((vidmode!=0x13) && (vidmode!=0x12) RAM[tempaddr32] = value; //is our video state a VGA mode? if not, write to regular RAM.
else writeVGA(tempaddr32-0xA0000, value); //if it's a VGA mode, send to VGA memory handler for processing (for unchained modes, etc)
} else {
RAM[tempaddr32] = value;
}
}
void push(uint16_t pushval) {
putreg16(regsp, getreg16(regsp) - 2); //decrement stack pointer by one word
putmem16(segregs[regss], getreg16(regsp), pushval); //shove our value into there
}
uint16_t pop() {
uint16_t tempval;
tempval = getmem16(segregs[regss], getreg16(regsp)); //get word from stack
putreg16(regsp, getreg16(regsp) + 2); //increment stack pointer by one word
return(tempval);
}
this next stuff is very important, the 8086 uses an addressing mode byte in most instructions. it contains three fields. the addressing mode (values 0 to 3), register field (0 to 7), and register/memory field (0 to 7)
the mode field indicates the meaning of the register/memory field. refer to the PDF i linked at the beginning of the post. also note that several instructions (called the "GRP" or group instructions) have an addressing mode byte, but the "register" field is actually used as an extension to determine the actual operation to be performed. yes, x86 is freakin' weird.
below are the functions that parse the addressing mode byte, and another one which is used to calculate the effective memory address based on the fields in that byte.
Code:
void modregrm() { //parse values from the addresing mode byte in many instructions
addrbyte = getmem8(segregs[regcs], ip); StepIP(1); //three bitfields in this, parsed by next 3 lines
mode = addrbyte >> 6;
reg = (addrbyte >> 3) & 7;
rm = addrbyte & 7;
switch (mode) {
case 0:
if (rm == 6) { disp16 = getmem16(segregs[regcs], ip); StepIP(2); }
if (((rm == 2) || (rm == 3)) && !segoverride) useseg = segregs[regss]; break;
case 1:
disp16 = signext(getmem8(segregs[regcs], ip)); StepIP(1);
if (((rm == 2) || (rm == 3) || (rm == 6)) && !segoverride) useseg = segregs[regss]; break;
case 2:
disp16 = getmem16(segregs[regcs], ip); StepIP(2);
if (((rm == 2) || (rm == 3) || (rm == 6)) && !segoverride) useseg = segregs[regss]; break;
default:
disp8 = 0; disp16 = 0;
}
}
void getea(uint8_t rmval) { //calculate effective address for the memory addressing modes
uint32_t tempea;
tempea = 0;
switch (mode) {
case 0:
switch (rmval) {
case 0: tempea = regs.wordregs[regbx] + regs.wordregs[regsi]; break;
case 1: tempea = regs.wordregs[regbx] + regs.wordregs[regdi]; break;
case 2: tempea = regs.wordregs[regbp] + regs.wordregs[regsi]; break;
case 3: tempea = regs.wordregs[regbp] + regs.wordregs[regdi]; break;
case 4: tempea = regs.wordregs[regsi]; break;
case 5: tempea = regs.wordregs[regdi]; break;
case 6: tempea = disp16; break;
case 7: tempea = regs.wordregs[regbx]; break;
} break;
case 1: case 2:
switch (rmval) {
case 0: tempea = regs.wordregs[regbx] + regs.wordregs[regsi] + disp16; break;
case 1: tempea = regs.wordregs[regbx] + regs.wordregs[regdi] + disp16; break;
case 2: tempea = regs.wordregs[regbp] + regs.wordregs[regsi] + disp16; break;
case 3: tempea = regs.wordregs[regbp] + regs.wordregs[regdi] + disp16; break;
case 4: tempea = regs.wordregs[regsi] + disp16; break;
case 5: tempea = regs.wordregs[regdi] + disp16; break;
case 6: tempea = regs.wordregs[regbp] + disp16; break;
case 7: tempea = regs.wordregs[regbx] + disp16; break;
} break;
}
ea = (tempea & 0xFFFF) + (useseg << 4);
}
the next functions read and write data from registers or memory depending on the meaning of the "register/memory" field in the addr mode byte:
Code:
uint16_t readrm16(uint8_t rmval) {
if (mode < 3) { //addressing mode table values lower than 3 indicate using RAM
getea(rmval); //go calculate effective address
return(read86(ea) | ((uint16_t)read86(ea+1)<<8));
} else { //mode table 3 means it's a register transaction
return(getreg16(rmval));
}
}
uint8_t readrm8(uint8_t rmval) {
if (mode < 3) { //addressing mode table values lower than 3 indicate using RAM
getea(rmval); //go calculate effective address
return(read86(ea));
} else { //mode table 3 means it's a register transaction
return(getreg8(rmval));
}
}
void writerm16(uint8_t rmval, uint16_t value) {
if (mode < 3) { //addressing mode table values lower than 3 indicate using RAM
getea(rmval); //go calculate effective address
write86(ea, value&0xFF);
write86(ea+1, value>>8);
} else { //mode table 3 means it's a register transaction
putreg16(rmval, value);
}
}
void writerm8(uint8_t rmval, uint8_t value) {
if (mode < 3) { //addressing mode table values lower than 3 indicate using RAM
getea(rmval); //go calculate effective address
write86(ea, value);
} else { //mode table 3 means it's a register transaction
putreg8(rmval, value);
}
}
the next instruction is very important, it handles interrupt calls by looking up the entry in the IVT at 0000:0000, pushes the flags, current CS and IP to the stack and then jumps to the handler described by the vector. there are a couple things it checks for first because my PC emu needs to handle some things on a higher level rather than let the actual BIOS ROM handle, like disk accesses on int 13h, etc..
Code:
void intcall86(uint8_t intnum) {
switch (intnum) {
case 0x10: //video services
if ((regs.byteregs[regah]==0x00) || (regs.byteregs[regah]==0x10)) {
vidinterrupt(); //intercept video mode change and palette change interrupt calls for high-level processing
}
break;
case 0x13: //disk services, don't let actual BIOS code handle it
diskhandler();
return;
case 0x16: //keyboard (the BIOS handles it, but this is to wrap extended keystroke requests to normal functions)
if ((regs.byteregs[regah]>=0x10) && (regs.byteregs[regah]<=0x12)) regs.byteregs[regah] -= 0x10;
break;
case 0x19: //bootstrap
if (verbose) { sprintf(msg, "BOOTSTRAP!\n"); print(msg); }
//read first sector of boot drive into 07C0:0000 and execute it
regs.byteregs[regdl] = bootdrive;
readdisk(regs.byteregs[regdl], 0x07C0, 0x0000, 0, 1, 0, 1);
segregs[regcs] = 0x0000; ip = 0x7C00;
return;
default: break;
}
push(makeflagsword()); //push flags word
push(segregs[regcs]); //push CS
push(ip); //push IP
segregs[regcs] = getmem16(0, ((uint16_t)intnum<<2)+2); //get interrupt vector to jump to from the IVT at 0x00000h
ip = getmem16(0, (uint16_t)intnum<<2);
ifl = 0; //clear interrupt flag
tf = 0; //clear trap flag
}
this next huge chunk is a big bucket of fun. most of the GRP opcodes have seperate functions here that check the register field in the addressing mode byte to determine the actual operation that should be performed. lots of stuff here, like GRP2 which is bit shifing, others handle NEG, NOT, MUL, DIV, etc.
Code:
uint8_t op_grp2_8(uint8_t cnt) {
uint16_t s, shift, oldcf, msb;
//if (cnt>0x8) return(0x7F); //80186+ limits shift count
s = oper1b;
oldcf = cf;
switch (reg) {
case 0: //ROL r/m8
for (shift=1; shift<=cnt; shift++) {
if (s & 0x80) cf = 1; else cf = 0;
s = s << 1;
s = s | cf;
}
if (cnt==1) of = cf ^ ((s >> 7) & 1);
break;
case 1: //ROR r/m8
for (shift=1; shift<=cnt; shift++) {
cf = s & 1;
s = (s >> 1) | (cf << 7);
}
if (cnt==1) of = (s >> 7) ^ ((s >> 6) & 1);
break;
case 2: //RCL r/m8
for (shift=1; shift<=cnt; shift++) {
oldcf = cf;
if (s & 0x80) cf = 1; else cf = 0;
s = s << 1;
s = s | oldcf;
}
if (cnt==1) of = cf ^ ((s >> 7) & 1);
break;
case 3: //RCR r/m8
for (shift=1; shift<=cnt; shift++) {
oldcf = cf;
cf = s & 1;
s = (s >> 1) | (oldcf << 7);
}
if (cnt==1) of = (s >> 7) ^ ((s >> 6) & 1);
break;
case 4: case 6: //SHL r/m8
for (shift=1; shift<=cnt; shift++) {
if (s & 0x80) cf = 1; else cf = 0;
s = (s << 1) & 0xFF;
}
if ((cnt==1) && (cf==(s>>7))) of = 0; else of = 1;
flag_szp8((uint8_t)s); break;
case 5: //SHR r/m8
if ((cnt==1) && (s & 0x80)) of = 1; else of = 0;
for (shift=1; shift<=cnt; shift++) {
cf = s & 1;
s = s >> 1;
}
flag_szp8((uint8_t)s); break;
case 7: //SAR r/m8
for (shift=1; shift<=cnt; shift++) {
msb = s & 0x80;
cf = s & 1;
s = (s >> 1) | msb;
}
of = 0;
flag_szp8((uint8_t)s); break;
}
return(s & 0xFF);
}
uint16_t op_grp2_16(uint8_t cnt) {
uint32_t s, shift, oldcf, msb;
//if (cnt>0x10) return(0x7FFF); //80186+ limits shift count
s = oper1;
oldcf = cf;
switch (reg) {
case 0: //ROL r/m8
for (shift=1; shift<=cnt; shift++) {
if (s & 0x8000) cf = 1; else cf = 0;
s = s << 1;
s = s | cf;
}
if (cnt==1) of = cf ^ ((s >> 15) & 1);
break;
case 1: //ROR r/m8
for (shift=1; shift<=cnt; shift++) {
cf = s & 1;
s = (s >> 1) | (cf << 15);
}
if (cnt==1) of = (uint8_t)((s >> 15) ^ ((s >> 14) & 1));
break;
case 2: //RCL r/m8
for (shift=1; shift<=cnt; shift++) {
oldcf = cf;
if (s & 0x8000) cf = 1; else cf = 0;
s = s << 1;
s = s | oldcf;
}
if (cnt==1) of = cf ^ ((s >> 15) & 1);
break;
case 3: //RCR r/m8
for (shift=1; shift<=cnt; shift++) {
oldcf = cf;
cf = s & 1;
s = (s >> 1) | (oldcf << 15);
}
if (cnt==1) of = (uint8_t)((s >> 15) ^ ((s >> 14) & 1));
break;
case 4: case 6: //SHL r/m8
for (shift=1; shift<=cnt; shift++) {
if (s & 0x8000) cf = 1; else cf = 0;
s = (s << 1) & 0xFFFF;
}
if ((cnt==1) && (cf==(s>>15))) of = 0; else of = 1;
flag_szp16((uint16_t)s); break;
case 5: //SHR r/m8
if ((cnt==1) && (s & 0x8000)) of = 1; else of = 0;
for (shift=1; shift<=cnt; shift++) {
cf = s & 1;
s = s >> 1;
}
flag_szp16((uint16_t)s); break;
case 7: //SAR r/m8
for (shift=1; shift<=cnt; shift++) {
msb = s & 0x8000;
cf = s & 1;
s = (s >> 1) | msb;
}
of = 0;
flag_szp16((uint16_t)s); break;
}
return((uint16_t)s & 0xFFFF);
}
void op_div8(uint16_t valdiv, uint8_t divisor) {
if (divisor==0) { intcall86(0); return; }
if ((valdiv / (uint16_t)divisor) > 0xFF) { intcall86(0); return; }
regs.byteregs[regah] = valdiv % (uint16_t)divisor;
regs.byteregs[regal] = valdiv / (uint16_t)divisor;
}
void op_idiv8(uint16_t valdiv, uint8_t divisor) {
uint16_t s1, s2, d1, d2;
int sign;
if (divisor==0) { intcall86(0); return; }
s1 = valdiv;
s2 = divisor;
sign = (((s1 ^ s2) & 0x8000) != 0);
s1 = (s1 < 0x8000) ? s1 : ((~s1 + 1) & 0xffff);
s2 = (s2 < 0x8000) ? s2 : ((~s2 + 1) & 0xffff);
d1 = s1 / s2;
d2 = s1 % s2;
if (d1 & 0xFF00) { intcall86(0); return; }
if (sign) {
d1 = (~d1 + 1) & 0xff;
d2 = (~d2 + 1) & 0xff;
}
regs.byteregs[regah] = (uint8_t)d2;
regs.byteregs[regal] = (uint8_t)d1;
}
#define op_grp3_8() {\
oper1 = signext(oper1b); oper2 = signext(oper2b);\
switch (reg) {\
case 0: case 1: /*TEST*/\
flag_log8(oper1b & getmem8(segregs[regcs], ip)); StepIP(1);\
break;\
\
case 2: /*NOT*/\
res8 = ~oper1b; break;\
\
case 3: /*NEG*/\
res8 = (~oper1b)+1;\
flag_sub8(0, oper1b);\
if (res8==0) cf = 0; else cf = 1;\
break;\
\
case 4: /*MUL*/\
temp1 = (uint32_t)oper1b * (uint32_t)regs.byteregs[regal];\
putreg16(regax, temp1 & 0xFFFF);\
flag_szp8((uint8_t)temp1);\
if (regs.byteregs[regah]) { cf = 1; of = 1; } else { cf = 0; of = 0; }\
break;\
\
case 5: /*IMUL*/\
oper1 = signext(oper1b);\
temp1 = signext(regs.byteregs[regal]);\
temp2 = oper1;\
if ((temp1 & 0x80)==0x80) temp1 = temp1 | 0xFFFFFF00;\
if ((temp2 & 0x80)==0x80) temp2 = temp2 | 0xFFFFFF00;\
temp3 = (temp1 * temp2) & 0xFFFF;\
putreg16(regax, temp3 & 0xFFFF);\
if (regs.byteregs[regah]) { cf = 1; of = 1; } else { cf = 0; of = 0; }\
break;\
\
case 6: /*DIV*/\
op_div8(getreg16(regax), oper1b);\
break;\
\
case 7: /*IDIV*/\
op_idiv8(getreg16(regax), oper1b);\
break;\
}\
}
void op_div16(uint32_t valdiv, uint16_t divisor) {
if (divisor==0) { intcall86(0); return; }
if ((valdiv / (uint32_t)divisor) > 0xFFFF) { intcall86(0); return; }
putreg16(regdx, (uint16_t)(valdiv % (uint32_t)divisor));
putreg16(regax, (uint16_t)(valdiv / (uint32_t)divisor));
}
void op_idiv16(uint32_t valdiv, uint16_t divisor) {
uint32_t d1, d2, s1, s2;
int sign;
if (divisor==0) { intcall86(0); return; }
s1 = valdiv;
s2 = divisor;
s2 = (s2 & 0x8000) ? (s2 | 0xffff0000) : s2;
sign = (((s1 ^ s2) & 0x80000000) != 0);
s1 = (s1 < 0x80000000) ? s1 : ((~s1 + 1) & 0xffffffff);
s2 = (s2 < 0x80000000) ? s2 : ((~s2 + 1) & 0xffffffff);
d1 = s1 / s2;
d2 = s1 % s2;
if (d1 & 0xFFFF0000) { intcall86(0); return; }
if (sign) {
d1 = (~d1 + 1) & 0xffff;
d2 = (~d2 + 1) & 0xffff;
}
putreg16(regax, (uint16_t)d1);
putreg16(regdx, (uint16_t)d2);
}
#define op_grp3_16() {\
switch (reg) {\
case 0: case 1: /*TEST*/\
flag_log16(oper1 & getmem16(segregs[regcs], ip)); StepIP(2); break;\
case 2: /*NOT*/\
res16 = ~oper1; break;\
case 3: /*NEG*/\
res16 = (~oper1) + 1;\
flag_sub16(0, oper1);\
if (res16) cf = 1; else cf = 0;\
break;\
case 4: /*MUL*/\
temp1 = (uint32_t)oper1 * (uint32_t)getreg16(regax);\
putreg16(regax, temp1 & 0xFFFF);\
putreg16(regdx, temp1 >> 16);\
flag_szp16((uint16_t)temp1);\
if (getreg16(regdx)) { cf = 1; of = 1; } else { cf = 0; of = 0; }\
break;\
case 5: /*IMUL*/\
temp1 = getreg16(regax);\
temp2 = oper1;\
if (temp1 & 0x8000) temp1 |= 0xFFFF0000;\
if (temp2 & 0x8000) temp2 |= 0xFFFF0000;\
temp3 = temp1 * temp2;\
putreg16(regax, temp3 & 0xFFFF); /*into register ax*/\
putreg16(regdx, temp3 >> 16); /*into register dx*/\
if (getreg16(regdx)) { cf = 1; of = 1; } else { cf = 0; of = 0; }\
break;\
case 6: /*DIV*/\
op_div16(((uint32_t)getreg16(regdx)<<16) + getreg16(regax), oper1); break;\
case 7: /*DIV*/\
op_idiv16(((uint32_t)getreg16(regdx)<<16) + getreg16(regax), oper1); break;\
}\
}
#define op_grp5() {\
switch (reg) {\
case 0: /* INC Ev */\
oper2 = 1;\
tempcf = cf;\
op_add16();\
cf = tempcf;\
writerm16(rm, res16); break;\
case 1: /* DEC Ev */\
oper2 = 1;\
tempcf = cf;\
op_sub16();\
cf = tempcf;\
writerm16(rm, res16); break;\
case 2: /* CALL Ev */\
push(ip);\
ip = oper1; break;\
case 3: /* CALL Mp */\
push(segregs[regcs]); push(ip);\
getea(rm);\
ip = (uint16_t)read86(ea) + (uint16_t)read86(ea + 1) * 256;\
segregs[regcs] = (uint16_t)read86(ea + 2) + (uint16_t)read86(ea + 3) * 256; break;\
case 4: /* JMP Ev */\
ip = oper1; break;\
case 5: /* JMP Mp */\
getea(rm);\
ip = (uint16_t)read86(ea) + (uint16_t)read86(ea + 1) * 256;\
segregs[regcs] = (uint16_t)read86(ea + 2) + (uint16_t)read86(ea + 3) * 256; break;\
case 6: /* PUSH Ev */\
push(oper1); break;\
case 7: /*invalid */\
intcall86(6);\
break;\
}\
}
the rest is too much, it won't let me paste in a single post.
Last edited: