Skip to content

8086tiny

Christian Mayer edited this page Aug 13, 2018 · 12 revisions

For this project I used 8086tiny as a reference to understand how the Intel 8086 works.

This page should help to understand the 8086tiny functions better.

CAST macro

// Reinterpretation cast
#define CAST(a) *(a*)&

TOP_BIT macro

// Returns number of top bit in operand (i.e. 8 for 8-bit operands, 16 for 16-bit operands)
#define TOP_BIT 8 * (i_w + 1)
if (i_w) {
	16;
} else {
	8;
}

GET_REG_ADDR macro

#define GET_REG_ADDR(reg_id) (REGS_BASE + (i_w ? 2 * reg_id : 2 * reg_id + reg_id / 4 & 7))
REGS_BASE + (
	i_w
		? 2 * reg_id
		: 2 * reg_id + reg_id / 4 & 7
	)

SEGREG macro

#define SEGREG(reg_seg,reg_ofs,op) 16 * regs16[reg_seg] + (unsigned short)(op regs16[reg_ofs])

DECODE_RM_REG macro

#define DECODE_RM_REG scratch2_uint = 4 * !i_mod, \
					  op_to_addr = rm_addr = i_mod < 3 ? SEGREG(seg_override_en ? seg_override : bios_table_lookup[scratch2_uint + 3][i_rm], bios_table_lookup[scratch2_uint][i_rm], regs16[bios_table_lookup[scratch2_uint + 1][i_rm]] + bios_table_lookup[scratch2_uint + 2][i_rm] * i_data1+) : GET_REG_ADDR(i_rm), \
					  op_from_addr = GET_REG_ADDR(i_reg), \
					  i_d && (scratch_uint = op_from_addr, op_from_addr = rm_addr, op_to_addr = scratch_uint)
scratch2_uint = 4 * !i_mod;
 
if (i_mod < 3){
    if (seg_override_en){
        reg_seg = seg_override;
    } else {
        reg_seg = bios_table_lookup[scratch2_uint + 3][i_rm];
    }
   
    reg_ofs = bios_table_lookup[scratch2_uint][i_rm];
   
    op_to_addr = rm_addr =
        16 * regs16[reg_seg]
        + (unsigned short)(
            regs16[bios_table_lookup[scratch2_uint + 1][i_rm]]
            + bios_table_lookup[scratch2_uint + 2][i_rm] * i_data1
            + regs16[reg_ofs]
        );
}else{
    op_to_addr = rm_addr = 
    	REGS_BASE + (i_w ? 2 * i_rm : 2 * i_rm + i_rm / 4 & 7);
}

op_from_addr = REGS_BASE + (i_w ? 2 * i_reg : 2 * i_reg + i_reg / 4 & 7);
 
if (i_d){
    scratch_uint = op_from_addr;
    op_from_addr = rm_addr;
    op_to_addr = scratch_uint;
}

R_M_OP macro

#define R_M_OP(dest,op,src) (i_w ? op_dest = CAST(unsigned short)dest, op_result = CAST(unsigned short)dest op (op_source = CAST(unsigned short)src) \
								 : (op_dest = dest, op_result = dest op (op_source = CAST(unsigned char)src)))
if (i_w) {
	op_dest = CAST(unsigned short)dest;
	op_source = CAST(unsigned short)src;
	op_result = CAST(unsigned short)dest op op_source;
} else {
	op_dest = dest;
	op_source = CAST(unsigned char)src;
	op_result = dest op (op_source);
}

SIGN_OF macro

// Returns sign bit of an 8-bit or 16-bit operand
#define SIGN_OF(a) (1 & (i_w ? CAST(short)a : a) >> (TOP_BIT - 1))
if (i_w) {
	(1 & CAST(short)a) >> (8 * (i_w + 1) - 1);
} else {
	(1 &            a) >> (8 * (i_w + 1) - 1);
}

MUL_MACRO macro

// [I]MUL/[I]DIV/DAA/DAS/ADC/SBB helpers
#define MUL_MACRO(op_data_type,out_regs) (set_opcode(0x10), \
										  out_regs[i_w + 1] = (op_result = CAST(op_data_type)mem[rm_addr] * (op_data_type)*out_regs) >> 16, \
										  regs16[REG_AX] = op_result, \
										  set_OF(set_CF(op_result - (op_data_type)op_result)))
(
	set_opcode(0x10);
	op_result = CAST(op_data_type)mem[rm_addr] * (op_data_type)*out_regs;
	out_regs[i_w + 1] = op_result >> 16;
	regs16[REG_AX] = op_result;
	
	int setCf = op_result - (op_data_type)op_result;
	set_CF(setCf);
	set_OF(setCf);
)

DIV_MACRO macro

#define DIV_MACRO(out_data_type,in_data_type,out_regs) (scratch_int = CAST(out_data_type)mem[rm_addr]) && !(scratch2_uint = (in_data_type)(scratch_uint = (out_regs[i_w+1] << 16) + regs16[REG_AX]) / scratch_int, scratch2_uint - (out_data_type)scratch2_uint) ? out_regs[i_w+1] = scratch_uint - scratch_int * (*out_regs = scratch2_uint) : pc_interrupt(0)
scratch_int = CAST(out_data_type)mem[rm_addr];
scratch_uint = (out_regs[i_w+1] << 16) + regs16[REG_AX];
scratch2_uint = (in_data_type)scratch_uint / scratch_int;

if(scratch_int && !(scratch2_uint - (out_data_type)scratch2_uint)){
	*out_regs = scratch2_uint;
	out_regs[i_w+1] = scratch_uint - scratch_int * *out_regs;
} else {
	pc_interrupt(0);
}

ADC_SBB_MACRO macro

#define ADC_SBB_MACRO(a) OP(a##= regs8[FLAG_CF] +), \
						 set_CF(regs8[FLAG_CF] && (op_result == op_dest) || (a op_result < a(int)op_dest)), \
						 set_AF_OF_arith()
OP(a##= regs8[FLAG_CF] +);
set_CF(regs8[FLAG_CF] && (op_result == op_dest) || (a op_result < a(int)op_dest));
set_AF_OF_arith();

INDEX_INC macro

#define INDEX_INC(reg_id) (regs16[reg_id] -= (2 * regs8[FLAG_DF] - 1)*(i_w + 1))
regs16[reg_id] -= (2 * regs8[FLAG_DF] - 1) * (i_w + 1)

Conditional jump (JAE, JNAE, etc.)

reg_ip += (char)i_data0 * (
  i_w ^ (
    regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_A][scratch_uchar]]
    || regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_B][scratch_uchar]]
    || regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_C][scratch_uchar]] ^ regs8[bios_table_lookup[TABLE_COND_JUMP_DECODE_D][scratch_uchar]]
  )
);

KEYBOARD_DRIVER macro

#define KEYBOARD_DRIVER read(0, mem + 0x4A6, 1) && (int8_asap = (mem[0x4A6] == 0x1B), pc_interrupt(7))
int x = read(0, mem + 0x4A6, 1);
if (x){
	int8_asap = mem[0x4A6] == 0x1B;
	pc_interrupt(7);
}

Zero Segment (ZS) and always-Zero Flag (XF)

When you look at the 8086tiny.c source code there is the FLAG_XF macro missing:

#define FLAG_CF 40
#define FLAG_PF 41
#define FLAG_AF 42
#define FLAG_ZF 43
#define FLAG_SF 44
#define FLAG_TF 45
#define FLAG_IF 46
#define FLAG_DF 47
#define FLAG_OF 48

This is the reason why this flag is set manually to 0 in the bios:

xor	ax, ax
mov	di, 24
stosw			; Set ZS = 0
mov	di, 49
stosb			; Set XF = 0

But this is actually wrong. When we translate the FLAG_XF (number 49) to the real flag bit number we get 12. Regarding to Wikipedia flags 12 to 15 are reserved and should always be 1.