use crate::*;
const DATA_ADDR: u64 = 0x7000;
const IE_BIT: u16 = 0x0001; const DE_BIT: u16 = 0x0002; const ZE_BIT: u16 = 0x0004; const OE_BIT: u16 = 0x0008; const UE_BIT: u16 = 0x0010; const PE_BIT: u16 = 0x0020; const SF_BIT: u16 = 0x0040; const ES_BIT: u16 = 0x0080; const B_BIT: u16 = 0x8000;
const EXCEPTION_MASK: u16 = IE_BIT | DE_BIT | ZE_BIT | OE_BIT | UE_BIT | PE_BIT;
fn write_u16(mem: u64, addr: u64, val: u16) {
let mut emu = emu64(); emu.maps.write_bytes_slice(addr, &val.to_le_bytes());
}
fn read_u16(mem: u64, addr: u64) -> u16 {
let emu = emu64(); let mut buf = [0u8; 2];
emu.maps.read_bytes_buff(&mut buf, addr);
u16::from_le_bytes(buf)
}
fn write_f64(mem: u64, addr: u64, val: f64) {
let mut emu = emu64(); emu.maps.write_bytes_slice(addr, &val.to_le_bytes());
}
#[test]
fn test_fnclex_basic() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & EXCEPTION_MASK, 0, "Exception flags should be cleared");
}
#[test]
fn test_fnclex_clears_exception_flags() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDD, 0x3C, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & EXCEPTION_MASK, 0, "FNCLEX should clear all exception flags");
}
#[test]
fn test_fnclex_multiple_times() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x02, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status1 = emu.maps.read_word(0x3000).unwrap();
let status2 = emu.maps.read_word(0x3002).unwrap();
assert_eq!(status1 & EXCEPTION_MASK, 0, "First FNCLEX should clear exceptions");
assert_eq!(status2 & EXCEPTION_MASK, 0, "Second FNCLEX should clear exceptions");
}
#[test]
fn test_fnclex_clears_es_bit() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & ES_BIT, 0, "ES bit should be cleared");
}
#[test]
fn test_fnclex_clears_sf_bit() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & SF_BIT, 0, "SF bit should be cleared");
}
#[test]
fn test_fclex_basic() {
let mut emu = emu64(); let code = [
0x9B, 0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & EXCEPTION_MASK, 0, "FCLEX should clear exception flags");
}
#[test]
fn test_fclex_clears_exception_flags() {
let mut emu = emu64(); let code = [
0x9B, 0xDB, 0xE2, 0xDD, 0x3C, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & EXCEPTION_MASK, 0, "FCLEX should clear all exception flags");
}
#[test]
fn test_fclex_vs_fnclex() {
let mut emu = emu64(); let code1 = [
0x9B, 0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
let code2 = [
0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code1);
emu.run(None).unwrap();
let status1 = emu.maps.read_word(0x3000).unwrap();
emu.load_code_bytes(&code2);
emu.run(None).unwrap();
let status2 = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status1, status2, "FCLEX and FNCLEX should give same result");
}
#[test]
fn test_fnclex_then_fnstsw() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x02, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status1 = emu.maps.read_word(0x3000).unwrap();
let status2 = emu.maps.read_word(0x3002).unwrap();
assert_eq!(status1 & EXCEPTION_MASK, 0, "First FNSTSW should show cleared exceptions");
assert_eq!(status2 & EXCEPTION_MASK, 0, "Second FNSTSW should show cleared exceptions");
}
#[test]
fn test_fnclex_before_operations() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDD, 0x04, 0x25, 0x00, 0x20, 0x00, 0x00, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xDD, 0x1C, 0x25, 0x08, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.maps.write_f64(0x2000, 2.5);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & EXCEPTION_MASK, 0, "Exceptions should still be cleared after FLD");
}
#[test]
fn test_fnclex_after_operations() {
let mut emu = emu64(); let code = [
0xDD, 0x04, 0x25, 0x00, 0x20, 0x00, 0x00, 0xDD, 0x04, 0x25, 0x08, 0x20, 0x00, 0x00, 0xDE, 0xC1, 0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xDD, 0x1C, 0x25, 0x08, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.maps.write_f64(0x2000, 1.5);
emu.maps.write_f64(0x2008, 2.5);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & EXCEPTION_MASK, 0, "FNCLEX should clear exceptions after arithmetic");
}
#[test]
fn test_fnclex_individual_exception_bits() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & IE_BIT, 0, "IE bit should be cleared");
assert_eq!(status & DE_BIT, 0, "DE bit should be cleared");
assert_eq!(status & ZE_BIT, 0, "ZE bit should be cleared");
assert_eq!(status & OE_BIT, 0, "OE bit should be cleared");
assert_eq!(status & UE_BIT, 0, "UE bit should be cleared");
assert_eq!(status & PE_BIT, 0, "PE bit should be cleared");
}
#[test]
fn test_sequential_fnclex() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDB, 0xE2, 0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & EXCEPTION_MASK, 0, "Multiple FNCLEX should clear all exceptions");
}
#[test]
fn test_fnclex_after_comparison() {
let mut emu = emu64(); let code = [
0xDD, 0x04, 0x25, 0x00, 0x20, 0x00, 0x00, 0xDD, 0x04, 0x25, 0x08, 0x20, 0x00, 0x00, 0xD8, 0xD1, 0xDB, 0xE2, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xDD, 0x1C, 0x25, 0x08, 0x30, 0x00, 0x00, 0xDD, 0x1C, 0x25, 0x10, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.maps.write_f64(0x2000, 5.0);
emu.maps.write_f64(0x2008, 5.0);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & EXCEPTION_MASK, 0, "Exception flags should be cleared");
}
#[test]
fn test_fnclex_complete_flow() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDD, 0x04, 0x25, 0x00, 0x20, 0x00, 0x00, 0xDD, 0x04, 0x25, 0x08, 0x20, 0x00, 0x00, 0xDE, 0xC1, 0xDD, 0x3C, 0x25, 0x00, 0x30, 0x00, 0x00, 0xDB, 0xE2, 0xDD, 0x3C, 0x25, 0x02, 0x30, 0x00, 0x00, 0xDD, 0x1C, 0x25, 0x08, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.maps.write_f64(0x2000, 1.5);
emu.maps.write_f64(0x2008, 2.5);
emu.run(None).unwrap();
let status_before = emu.maps.read_word(0x3000).unwrap();
let status_after = emu.maps.read_word(0x3002).unwrap();
assert_eq!(status_after & EXCEPTION_MASK, 0, "Exceptions should be cleared after FNCLEX");
}
#[test]
fn test_fclex_multiple_operations() {
let mut emu = emu64(); let code = [
0xDD, 0x04, 0x25, 0x00, 0x20, 0x00, 0x00, 0xDD, 0x04, 0x25, 0x08, 0x20, 0x00, 0x00, 0xDE, 0xC1, 0x9B, 0xDB, 0xE2, 0xDD, 0x04, 0x25, 0x10, 0x20, 0x00, 0x00, 0xDE, 0xC1, 0xDF, 0xE0, 0x66, 0x89, 0x04, 0x25, 0x00, 0x30, 0x00, 0x00, 0xDD, 0x1C, 0x25, 0x08, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.maps.write_f64(0x2000, 1.0);
emu.maps.write_f64(0x2008, 2.0);
emu.maps.write_f64(0x2010, 3.0);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & EXCEPTION_MASK, 0, "Exceptions should remain cleared");
}
#[test]
fn test_fnclex_status_word_clean_state() {
let mut emu = emu64(); let code = [
0xDB, 0xE2, 0xDD, 0x3C, 0x25, 0x00, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.run(None).unwrap();
let status = emu.maps.read_word(0x3000).unwrap();
assert_eq!(status & IE_BIT, 0);
assert_eq!(status & DE_BIT, 0);
assert_eq!(status & ZE_BIT, 0);
assert_eq!(status & OE_BIT, 0);
assert_eq!(status & UE_BIT, 0);
assert_eq!(status & PE_BIT, 0);
assert_eq!(status & ES_BIT, 0);
assert_eq!(status & SF_BIT, 0);
}
#[test]
fn test_fnclex_preserves_other_bits() {
let mut emu = emu64(); let code = [
0xDD, 0x04, 0x25, 0x00, 0x20, 0x00, 0x00, 0xDD, 0x3C, 0x25, 0x00, 0x30, 0x00, 0x00, 0xDB, 0xE2, 0xDD, 0x3C, 0x25, 0x02, 0x30, 0x00, 0x00, 0xDD, 0x1C, 0x25, 0x08, 0x30, 0x00, 0x00, 0xF4, ];
emu.load_code_bytes(&code);
emu.maps.write_f64(0x2000, 3.14159);
emu.run(None).unwrap();
let status_before = emu.maps.read_word(0x3000).unwrap();
let status_after = emu.maps.read_word(0x3002).unwrap();
let top_before = (status_before >> 11) & 0x7;
let top_after = (status_after >> 11) & 0x7;
assert_eq!(top_before, top_after, "TOP should be preserved by FNCLEX");
}