/// <summary> /// Add left and right ALU buffers, put the result on the internal data bus, update the Flags accordingly /// </summary> /// <param name="withInputCarry">If true, add one when the CF flag is set before the operation</param> /// <param name="withOutputCarry">If true, update the value of the CF flag after operation</param> /// <param name="with16bitsZFlag">If true, update the Z flag according to 16 bits arithmetic semantics</param> /// <param name="withSZPFlags">If false, do not update the S, Z, and P/V flags</param> /// <param name="withComparisonYXFlags">If true, copy Y and X flags from the right buffer instead of the result</param> private void ExecuteArithmeticOperation(ArithmeticOperationType operation, bool withInputCarry, bool withOutputCarry, bool with16bitsZFlag, bool withSZPFlags, bool withComparisonYXFlags) { // Carry flag (bit 0) is considered only in case of add or sub with carry int inputCarry = 0; if (withInputCarry) { inputCarry = F & B00000001; } // For 16 bits operations performed in 2 stages, check if the previous operation result was Zero bool previousOperationResultWasZero = ZF; // Perform the operation using our host CPU int result = 0; switch (operation) { case ArithmeticOperationType.Addition: result = ALULeftBuffer + ALURightBuffer + inputCarry; break; case ArithmeticOperationType.LeftMinusRight: result = ALULeftBuffer - ALURightBuffer - inputCarry; break; case ArithmeticOperationType.RightMinusLeft: result = ALURightBuffer - ALULeftBuffer - inputCarry; break; } // Compute the carries associated with this operation afterwards. int carries = (ALULeftBuffer ^ ALURightBuffer ^ result) >> 1; // Explanation of the formula above : // Consider bit n of the result : if no carry was forwarded from the previous bit, this bit is set if a and b bits are different (0 + 1 = 1 + 0 = 1), reset if a and b bits are the same (0 + 0 = 0, 1 + 1 = 0 + carry for next bit). // On the contrary, a carry must have been forwarded from bit n-1 to bit n : if a and b bits are different (a ^ b = 1) and result is reset (result = 0), or if a and b bits are the same (a ^ b = 0) and result is set (result = 1). // Formula : carry[n-1 to n] = ((a[n] ^ b[n] == 1 && result[n] == 0) || (a[n] ^ b[n] == 0 & result[n] == 1)). // Simplification : carry[n-1 to n] = a[n] ^ b[n] ^ result[n]. // In the variable "carries" below, when bit n is set it means that there was a carry from bit n to bit n+1 during the addition. // Truncate the result to 8 bits byte truncResult = (byte)result; // and send it to the internal data bus. InternalDataBus = truncResult; // Compute flags // Prepare a binary mask to preserve the flags which are not affected by the current operation byte notAffectedFlagsMask = 0; if (!withOutputCarry) { notAffectedFlagsMask |= B00000001; } if(!withSZPFlags) { notAffectedFlagsMask |= B11000100; } // Preserve all flags which are not affected by the current operation F = (byte)(F & notAffectedFlagsMask); if (withSZPFlags) { // Flags bit 7 - SF flag - Set if the 2-complement value is negative. It's simply a copy of the most signifcant bit of the result. F |= (byte)(truncResult & B10000000); // Flags bit 6 - ZF flag Set if the result is zero. if (truncResult == 0 && (!with16bitsZFlag || previousOperationResultWasZero)) F |= B01000000; // Flags bit 2 - VF flag 2-compliment signed overflow : set if 2-compliment value doesn't fit in the register. F |= (byte)(((carries & B10000000) >> 5) ^ ((carries & B01000000) >> 4)); // Explanation of the formula above : // The idea behind setting the overflow flag is simple. Suppose you sign-extend your 8-bit signed integers to 9 bits, that is, just copy the 7th bit to an extra, 8th bit. An overflow/underflow will occur if the 9-bit sum/difference of these 9-bit signed integers has different values in bits 7 and 8, meaning that the addition/subtraction has lost the result's sign in the 7th bit and used it for the result's magnitude, or, in other words, the 8 bits can't accommodate the sign bit and such a large magnitude. // Now, bit 7 of the result can differ from the imaginary sign bit 8 if and only if the carry into bit 7 and the carry into bit 8 (=carry out of bit 7) are different. That's because we start with the addends having bit 7=bit 8 and only different carry-ins into them can affect them in the result in different ways. // So overflow flag = carry-out flag XOR carry from bit 6 into bit 7. } // Undocumented - Flags bits 5 and 3 if (!withComparisonYXFlags) { // Flags bit 5 - YF flag - A copy of bit 5 of the result. // Flags bit 3 - XF flag - A copy of bit 3 of the result. F |= (byte)(truncResult & B00101000); } else { // Special case for comparison operation : YF and XF flags are copied from the right operand, not the result. F |= (byte)(ALURightBuffer & B00101000); } // Flags bit 4 - HF flag The half-carry of an addition/subtraction (from bit 3 to 4). Needed for BCD correction with DAA. F |= (byte)((carries & B00001000) << 1); // Flags bit 1 - NF flag Shows whether the last operation was an addition (0) or a subtraction (1). This information is needed for DAA. if (operation != ArithmeticOperationType.Addition) { F |= B00000010; } if (withOutputCarry) { // Flags bit 0 - CF flag The carry flag, set if there was a carry after the most significant bit. F |= (byte)((carries & B10000000) >> 7); } }
public ArithmeticBinaryOperation(ArithmeticOperationType type, Node lhs, Node rhs) { this.Type = type; this.Lhs = lhs; this.Rhs = rhs; }
/// <summary> /// Add left and right ALU buffers, put the result on the internal data bus, update the Flags accordingly /// </summary> /// <param name="withInputCarry">If true, add one when the CF flag is set before the operation</param> /// <param name="withOutputCarry">If true, update the value of the CF flag after operation</param> /// <param name="with16bitsZFlag">If true, update the Z flag according to 16 bits arithmetic semantics</param> /// <param name="withSZPFlags">If false, do not update the S, Z, and P/V flags</param> /// <param name="withComparisonYXFlags">If true, copy Y and X flags from the right buffer instead of the result</param> private void ExecuteArithmeticOperation(ArithmeticOperationType operation, bool withInputCarry, bool withOutputCarry, bool with16bitsZFlag, bool withSZPFlags, bool withComparisonYXFlags) { // Carry flag (bit 0) is considered only in case of add or sub with carry int inputCarry = 0; if (withInputCarry) { inputCarry = F & B00000001; } // For 16 bits operations performed in 2 stages, check if the previous operation result was Zero bool previousOperationResultWasZero = ZF; // Perform the operation using our host CPU int result = 0; switch (operation) { case ArithmeticOperationType.Addition: result = ALULeftBuffer + ALURightBuffer + inputCarry; break; case ArithmeticOperationType.LeftMinusRight: result = ALULeftBuffer - ALURightBuffer - inputCarry; break; case ArithmeticOperationType.RightMinusLeft: result = ALURightBuffer - ALULeftBuffer - inputCarry; break; } // Compute the carries associated with this operation afterwards. int carries = (ALULeftBuffer ^ ALURightBuffer ^ result) >> 1; // Explanation of the formula above : // Consider bit n of the result : if no carry was forwarded from the previous bit, this bit is set if a and b bits are different (0 + 1 = 1 + 0 = 1), reset if a and b bits are the same (0 + 0 = 0, 1 + 1 = 0 + carry for next bit). // On the contrary, a carry must have been forwarded from bit n-1 to bit n : if a and b bits are different (a ^ b = 1) and result is reset (result = 0), or if a and b bits are the same (a ^ b = 0) and result is set (result = 1). // Formula : carry[n-1 to n] = ((a[n] ^ b[n] == 1 && result[n] == 0) || (a[n] ^ b[n] == 0 & result[n] == 1)). // Simplification : carry[n-1 to n] = a[n] ^ b[n] ^ result[n]. // In the variable "carries" below, when bit n is set it means that there was a carry from bit n to bit n+1 during the addition. // Truncate the result to 8 bits byte truncResult = (byte)result; // and send it to the internal data bus. InternalDataBus = truncResult; // Compute flags // Prepare a binary mask to preserve the flags which are not affected by the current operation byte notAffectedFlagsMask = 0; if (!withOutputCarry) { notAffectedFlagsMask |= B00000001; } if (!withSZPFlags) { notAffectedFlagsMask |= B11000100; } // Preserve all flags which are not affected by the current operation F = (byte)(F & notAffectedFlagsMask); if (withSZPFlags) { // Flags bit 7 - SF flag - Set if the 2-complement value is negative. It's simply a copy of the most signifcant bit of the result. F |= (byte)(truncResult & B10000000); // Flags bit 6 - ZF flag Set if the result is zero. if (truncResult == 0 && (!with16bitsZFlag || previousOperationResultWasZero)) { F |= B01000000; } // Flags bit 2 - VF flag 2-compliment signed overflow : set if 2-compliment value doesn't fit in the register. F |= (byte)(((carries & B10000000) >> 5) ^ ((carries & B01000000) >> 4)); // Explanation of the formula above : // The idea behind setting the overflow flag is simple. Suppose you sign-extend your 8-bit signed integers to 9 bits, that is, just copy the 7th bit to an extra, 8th bit. An overflow/underflow will occur if the 9-bit sum/difference of these 9-bit signed integers has different values in bits 7 and 8, meaning that the addition/subtraction has lost the result's sign in the 7th bit and used it for the result's magnitude, or, in other words, the 8 bits can't accommodate the sign bit and such a large magnitude. // Now, bit 7 of the result can differ from the imaginary sign bit 8 if and only if the carry into bit 7 and the carry into bit 8 (=carry out of bit 7) are different. That's because we start with the addends having bit 7=bit 8 and only different carry-ins into them can affect them in the result in different ways. // So overflow flag = carry-out flag XOR carry from bit 6 into bit 7. } // Undocumented - Flags bits 5 and 3 if (!withComparisonYXFlags) { // Flags bit 5 - YF flag - A copy of bit 5 of the result. // Flags bit 3 - XF flag - A copy of bit 3 of the result. F |= (byte)(truncResult & B00101000); } else { // Special case for comparison operation : YF and XF flags are copied from the right operand, not the result. F |= (byte)(ALURightBuffer & B00101000); } // Flags bit 4 - HF flag The half-carry of an addition/subtraction (from bit 3 to 4). Needed for BCD correction with DAA. F |= (byte)((carries & B00001000) << 1); // Flags bit 1 - NF flag Shows whether the last operation was an addition (0) or a subtraction (1). This information is needed for DAA. if (operation != ArithmeticOperationType.Addition) { F |= B00000010; } if (withOutputCarry) { // Flags bit 0 - CF flag The carry flag, set if there was a carry after the most significant bit. F |= (byte)((carries & B10000000) >> 7); } }
private void DecimalAdjust() { // In BCD representation, each decimal numeral is represented by a four // bit pattern : http://en.wikipedia.org/wiki/Binary-coded_decimal. // Numbers between 00 and 99 can thus be encoded in one byte = 2 x 4 bits. // Bits : 7654 3210 // Decimal digits : highDigit lowDigit // For example, decimal number 85 is encoded as : // Bits : 1000 0101 // Decimal digits : 8 5 // It can simply be written as hexadecimal number 0x85. // This instruction is executed after an arithmetic operation, // addition (if flag NF = 0) or a subtraction (if flag NF = 1). ArithmeticOperationType previousOperation = ArithmeticOperationType.Addition; if (NF == true) { previousOperation = ArithmeticOperationType.LeftMinusRight; } // This instruction computes the value to add or subtract into ALURightBuffer, // to adjust the accumulator after a BCD operation (addition or subtraction). // The result of adding idependently each one of the decimal digits // of the two initial operands can be found by taking into account // the half carry (carry between bits 3 and 4) and the full carry // (between bit 7 and the next byte) produced by the operation. int resultAfterOperation = ALULeftBuffer; bool halfCarrySetDuringOperation = HF; bool fullCarrySetDuringOperation = CF; // If the addition of the first 4 bits gave a result // between 0 and 9, then no correction is necessary. // Example 1 : 0x4 + 0x5 = 0x9, the result is correct. int lowNibbleAfterOperation = resultAfterOperation & B00001111; int correction = 0; // But if the addition / substraction of the first 4 bits gave a result // at least equal to ten (between A and F or with a half carry), then we // must translate the number from base 16 (4 bits) to base 10, and produce // a half carry to increment the next digit in base 10 if necessary. // To do this, we just have to add 6 to the result (2^4 - 10). // Example 2 : 0x6 + 0x7 = 0xD, adjust with +6 : 0xD + 6 = 0x13. // Example 3 : 0x8 + 0x9 = 0x11, adjust with +6 : 0x11 + 6 = 0x17. // After the correction, a new half carry will be propagated to the // higher 4 bits only if the value of the low nibble is between // 0xA and 0xF. if (lowNibbleAfterOperation > 0x09 || halfCarrySetDuringOperation) { correction = 6; } // Apply the same kind of correction to the higher digit if (resultAfterOperation > 0x99 || fullCarrySetDuringOperation) { correction += 0x60; } // Load into ALURightBuffer the correction to add or subtract ALURightBuffer = (byte)correction; // Execute the operation to adjust the result ExecuteArithmeticOperation(previousOperation, false, true, false, true, false); // For arithmetic operations, the flag at nit 2 indicates an Overflow condition // But with DAA instruction, is is used to indicate the resulting parity is Even // The number of 1 bits in a byte are counted. // If the total is Odd, ODD parity is flagged (P = 0). // If the total is Even, EVEN parity is flagged (P = 1). PF = numberOfBitsInByteParityTable[InternalDataBus]; // Adjust the carry flag to take both operation + correction into account CF = resultAfterOperation > 0x99 | fullCarrySetDuringOperation; if (TraceMicroInstructions) { TraceMicroInstruction(new MicroInstruction(Z80MicroInstructionTypes.ArithmeticOperationDecimalAdjust)); } }
public ArithmeticOperation(int coefficient, ArithmeticOperationType type) { _coefficient = coefficient; _type = type; }