const expect = require('chai').expect; const MOSCIA = require('../src/MOS/CIA.js'); describe("MOSCIA Tests...", function(){ //var cia = new MOSCIA(); it("R/W Pin", function(){ let cia = new MOSCIA(); // The RW pin is default low, so writting should be enabled. cia.RS = 0x02; expect(cia.DATA).to.be.equal(0x00); cia.DATA = 0xFF; expect(cia.DATA).to.be.equal(0xFF); // Set RW high to disable writting! cia.RW = 1; cia.DATA = 0x00; expect(cia.DATA).to.be.equal(0xFF); // Set it low once more cia.RW = 0; cia.DATA = 0x00; expect(cia.DATA).to.be.equal(0x00); }); it("Interrupts (via FLAG)", function(){ let cia = new MOSCIA(); cia.FLAG = 1; cia.RS = 0x0D; let IC = cia.DATA; expect(IC & 0x10).to.be.equal(0x10); expect(IC & 0x80).to.be.equal(0x80); expect(cia.DATA).to.be.equal(0x00); // Setting data changes the interrupt mask. This should mask the // FLAG interrupt. cia.DATA = 0x90; cia.FLAG = 1; IC = cia.DATA; expect(IC).to.be.equal(0x00); }); it("PD*/DD* Masking", function(){ let cia = new MOSCIA(); let pdaVal = 0x3C; let pdbVal = 0xC3; cia.onRead("PDA", (name, dda) => { if (dda < 255) cia.setPDA(pdaVal); }); cia.onRead("PDB", (name, ddb) => { if (ddb < 255) cia.setPDB(pdbVal); }); cia.RS = 0x02; cia.DATA = 0x00; cia.RS = 0x00; expect(cia.DATA).to.be.equal(pdaVal); cia.RS = 0x03; cia.DATA = 0x00; cia.RS = 0x01; expect(cia.DATA).to.be.equal(pdbVal); cia.RS = 0x02; cia.DATA = pdbVal; cia.RS = 0x00; cia.DATA = pdbVal; expect(cia.DATA).to.be.equal(0xFF); cia.RS = 0x03; cia.DATA = pdaVal; cia.RS = 0x01; cia.DATA = pdaVal; expect(cia.DATA).to.be.equal(0xFF); }); it("Serial IO", function(){ let cia = new MOSCIA(); let Input = (i) => { cia.SP = i; cia.CNT = 1; cia.phi2 = 1; }; var outval = 0; let Output = () => { cia.CNT = 1; cia.phi2 = 1; outval = (outval << 1) | cia.SP; } cia.RS = 0x0C; Input(1); expect(cia.DATA).to.be.equal(1); Input(1); expect(cia.DATA).to.be.equal(3); Input(0); expect(cia.DATA).to.be.equal(6); Input(0); Input(1); Input(1); Input(0); Input(1); expect(cia.DATA).to.be.equal(0xCD); cia.RS = 0x0D; let IC = cia.DATA; expect(IC & 0x08).to.be.equal(0x08); expect(IC & 0x80).to.be.equal(0x80); // Reading from RS = 0x0D should clear the interrupt values. Checking this expect(cia.DATA).to.be.equal(0); cia.RS = 0x0E; cia.DATA = 0x40; // Enable serial output. Output(); expect(outval).to.be.equal(1); Output(); expect(outval).to.be.equal(3); Output(); expect(outval).to.be.equal(6); Output(); Output(); Output(); Output(); Output(); expect(outval).to.be.equal(0xCD); cia.RS = 0x0C; expect(cia.DATA).to.be.equal(0); cia.RS = 0x0D; expect((cia.DATA & 0x08) >> 3).to.be.equal(1); expect(cia.DATA).to.be.equal(0); }); it("TOD 60hz", function(){ // NOTE: The TOD clock doesn't actually enforce the frequency. // The TOD 'pin' is expected to be attached to a clock pulsing at the // designated frequency. // As such, this test will run MUCH MUCH faster than the 1.1 seconds // actually being checked (because I'm not using a clock here). let cia = new MOSCIA(); let tick60 = () => { for (let i=0; i < 6; i++) cia.TOD = 1; }; // NOTE: The CIA defaults to 60hz TOD clock. Nothing needs to be set. // Check that seconds is at 0 (we'll test this again later) cia.RS = 0x09; expect(cia.DATA).to.be.equal(0); // Switch to 10ths of a second and go! cia.RS = 0x08; expect(cia.DATA).to.be.equal(0); tick60(); expect(cia.DATA).to.be.equal(0x01); tick60(); expect(cia.DATA).to.be.equal(0x02); tick60(); expect(cia.DATA).to.be.equal(0x03); for (let i=0; i < 8; i++) tick60(); // Should have looped back around a little at this point! expect(cia.DATA).to.be.equal(0x01); // Check seconds again. This should have gone up once! cia.RS = 0x09; expect(cia.DATA).to.be.equal(0x01); }); it("TOD 50hz", function(){ // NOTE: The TOD clock doesn't actually enforce the frequency. // The TOD 'pin' is expected to be attached to a clock pulsing at the // designated frequency. // As such, this test will run MUCH MUCH faster than the 1.1 seconds // actually being checked (because I'm not using a clock here). let cia = new MOSCIA(); let tick50 = () => { for (let i=0; i < 5; i++) cia.TOD = 1; }; // enabling 50 hz mode. cia.RS = 0x0E; cia.DATA = cia.DATA | 0x80; // Check that seconds is at 0 (we'll test this again later) cia.RS = 0x09; expect(cia.DATA).to.be.equal(0); // Switch to 10ths of a second and go! cia.RS = 0x08; expect(cia.DATA).to.be.equal(0); tick50(); expect(cia.DATA).to.be.equal(0x01); tick50(); expect(cia.DATA).to.be.equal(0x02); tick50(); expect(cia.DATA).to.be.equal(0x03); for (let i=0; i < 8; i++) tick50(); // Should have looped back around a little at this point! expect(cia.DATA).to.be.equal(0x01); // Check seconds again. This should have gone up once! cia.RS = 0x09; expect(cia.DATA).to.be.equal(0x01); }); it("TOD Latching", function(){ let cia = new MOSCIA(); let tick = (cycles) => { let ccount = cycles * 6; for (let i=0; i < ccount; i++) cia.TOD = 1; }; cia.setTOD(0x03, 0x59, 0x58, 0x00); // Copying current clock values for later reference. let TOD = [0,0,0,0]; cia.RS = 0x08; TOD[0] = cia.DATA; cia.RS = 0x09; TOD[1] = cia.DATA; cia.RS = 0x0A; TOD[2] = cia.DATA; cia.RS = 0x0B; TOD[3] = cia.DATA; // This should latch the timer!! tick(11); // Simulate 1.1 second time passing. // TOD Latch shouldn't be released until next read from RS=0x08, so... expect(cia.DATA).to.be.equal(TOD[3]); cia.RS = 0x0A; expect(cia.DATA).to.be.equal(TOD[2]); cia.RS = 0x09; expect(cia.DATA).to.be.equal(TOD[1]); cia.RS = 0x08; expect(cia.DATA).to.be.equal(TOD[0]); // Should release the TOD latch // With latch released, we should be able to read the new time... expect(cia.DATA).to.be.equal(0x01); cia.RS = 0x09; expect(cia.DATA).to.be.equal(0x59); }); it("TOD Time Rollover with AM/PM change", function(){ let cia = new MOSCIA(); let tick = () => { for (let i=0; i < 6; i++) cia.TOD = 1; }; cia.setTOD(0x11, 0x59, 0x59, 0x09); tick(); cia.RS = 0x0B; expect(cia.DATA).to.be.equal(0x80); cia.RS = 0x0A; expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x09; expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x08; expect(cia.DATA).to.be.equal(0x00); cia.setTOD(0x91, 0x59, 0x59, 0x09); tick(); cia.RS = 0x0B; expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x0A; expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x09; expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x08; expect(cia.DATA).to.be.equal(0x00); }); it("TOD Write Time and Cycle Stop on Write", function(){ let cia = new MOSCIA(); let tick = (cycles) => { let ccount = cycles * 6; for (let i=0; i < ccount; i++) cia.TOD = 1; }; cia.RS = 0x0B; cia.DATA = 0x11; cia.RS = 0x0A; cia.DATA = 0x30; cia.RS = 0x09; cia.DATA = 0x20; // The lock on the timer isn't released until a write to RS=0x08 // so running a few ticks should NOT change the current values! tick(10); cia.RS = 0x0B; expect(cia.DATA).to.be.equal(0x11); cia.RS = 0x0A; expect(cia.DATA).to.be.equal(0x30); cia.RS = 0x09; expect(cia.DATA).to.be.equal(0x20); cia.RS = 0x08; expect(cia.DATA).to.be.equal(0x00); // Write to RS=0x08 and cycle again! cia.DATA = 0x08; // <= 8/10ths a second. tick(8); expect(cia.DATA).to.be.equal(0x06); cia.RS = 0x09; expect(cia.DATA).to.be.equal(0x21); // Check if writing is prevented unless Hours (0x0B) is written to first cia.DATA = 0x44; expect(cia.DATA).to.be.equal(0x21); cia.RS = 0x08; cia.DATA = 0x04; expect(cia.DATA).to.be.equal(0x06); }); it("TOD Alarm Set and Trigger", function(){ let cia = new MOSCIA(); let tick = (cycles) => { let ccount = cycles * 6; for (let i=0; i < ccount; i++) cia.TOD = 1; }; // Setting alarm values. cia.RS = 0x0F; cia.DATA = cia.DATA | 0x80; cia.RS = 0x0B; cia.DATA = 0x01; cia.RS = 0x0A; cia.DATA = 0x01; cia.RS = 0x09; cia.DATA = 0x01; cia.RS = 0x08; cia.DATA = 0x04; // Testing that all TOD values remain unchanged. expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x09; expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x0A; expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x0B; expect(cia.DATA).to.be.equal(0x00); // Forcing TOD to be 4/10ths a second from triggering the alarm. cia.setTOD(0x01, 0x01, 0x01, 0x00); tick(4); cia.RS = 0x0D; // Hold the value as the IC clears after a read. let IC = cia.DATA; expect(IC & 0x04).to.be.equal(0x04); expect(IC & 0x80).to.be.equal(0x80); }); it("Timer A, phi2 Triggered, Interrupt Verification", function(){ let cia = new MOSCIA(); let tick = (cycles) => { for (let i=0; i < cycles; i++) cia.phi2 = 1; }; cia.RS = 0x04; cia.DATA = 0xFF; cia.RS = 0x05; cia.DATA = 0x01; cia.RS = 0x0E; // Force latch load into Timer A and activate Timer A cia.DATA = cia.DATA | 0x11; cia.RS = 0x04; expect(cia.DATA).to.be.equal(0xFF); tick(1); expect(cia.DATA).to.be.equal(0xFE); tick(1); expect(cia.DATA).to.be.equal(0xFD); tick(0x0200 - 3); expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x05; expect(cia.DATA).to.be.equal(0x00); tick(1); expect(cia.DATA).to.be.equal(0x01); cia.RS = 0x04; expect(cia.DATA).to.be.equal(0xFF); cia.RS = 0x0D; let IC = cia.DATA; expect(IC & 0x01).to.be.equal(0x01); expect(IC & 0x80).to.be.equal(0x80); }); it("Timer A, CNT Triggered, Underflow report to Port B", function(){ let cia = new MOSCIA(); let tick = (cycles, cnt) => { cnt = (cnt === true); for (let i=0; i < cycles; i++){ if (cnt) cia.CNT = 1; cia.phi2 = 1; if (cnt) cia.CNT = 0; } }; cia.RS = 0x04; cia.DATA = 0x08; cia.RS = 0x05; cia.DATA = 0x00; cia.RS = 0x0E; // Force latch load into Timer A, // enable underflow reporting on Port B bit 6, // set Timer A to trigger on CNT, // and activate Timer A cia.DATA = 0x33; cia.RS = 0x04; // First, test a few ticks where CNT is not high. expect(cia.DATA).to.be.equal(0x08); // Validate inital timer value. tick(1); tick(1); tick(1); tick(1); expect(cia.DATA).to.be.equal(0x08); // Now verify CNT triggers! tick(1, true); expect(cia.DATA).to.be.equal(0x07); tick(1, true); expect(cia.DATA).to.be.equal(0x06); tick(1, true); tick(1, true); tick(1, true); tick(1, true); tick(1, true); tick(1, true); expect(cia.DATA).to.be.equal(0x00); tick(1, true); // Double check that timer has reset to latch value... expect(cia.DATA).to.be.equal(0x08); // Verify Interrupt (again... but it doesn't hurt!) cia.RS = 0x0D; let IC = cia.DATA; expect(IC & 0x01).to.be.equal(0x01); expect(IC & 0x80).to.be.equal(0x80); // Checking Port B bit 6 which should only go high for 1 cycle! cia.RS = 0x01; expect(cia.DATA & 0x40).to.be.equal(0x40); tick(1, true); expect(cia.DATA & 0x40).to.be.equal(0x00); // Check Port B bit 6 toggling... // ------------------------------------------- // Force latch load into Timer A, // enable underflow reporting on Port B bit 6, // setup Port B bit 6 to invert, // set Timer A to trigger on CNT, // and activate Timer A cia.RS = 0x0E; cia.DATA = 0x37; for (let i=0; i < 9; i++) tick(1, true); cia.RS = 0x04; expect(cia.DATA).to.be.equal(0x08); cia.RS = 0x01; expect(cia.DATA & 0x40).to.be.equal(0x40); tick(1, true); expect(cia.DATA & 0x40).to.be.equal(0x40); cia.RS = 0x04; expect(cia.DATA).to.be.equal(0x07); for (let i=0; i < 8; i++) tick(1, true); expect(cia.DATA).to.be.equal(0x08); cia.RS = 0x01; expect(cia.DATA & 0x40).to.be.equal(0x00); }); it("Timer B, phi2 Triggered, Interrupt Verification", function(){ let cia = new MOSCIA(); let tick = (cycles) => { for (let i=0; i < cycles; i++) cia.phi2 = 1; }; cia.RS = 0x06; cia.DATA = 0xFF; cia.RS = 0x07; cia.DATA = 0x01; cia.RS = 0x0F; // Force latch load into Timer B and activate Timer B cia.DATA = cia.DATA | 0x11; cia.RS = 0x06; expect(cia.DATA).to.be.equal(0xFF); tick(1); expect(cia.DATA).to.be.equal(0xFE); tick(1); expect(cia.DATA).to.be.equal(0xFD); tick(0x0200 - 3); expect(cia.DATA).to.be.equal(0x00); cia.RS = 0x07; expect(cia.DATA).to.be.equal(0x00); tick(1); expect(cia.DATA).to.be.equal(0x01); cia.RS = 0x06; expect(cia.DATA).to.be.equal(0xFF); cia.RS = 0x0D; let IC = cia.DATA; expect(IC & 0x02).to.be.equal(0x02); expect(IC & 0x80).to.be.equal(0x80); }); it("Timer B, CNT Triggered, Underflow report to Port B", function(){ let cia = new MOSCIA(); let tick = (cycles, cnt) => { cnt = (cnt === true); for (let i=0; i < cycles; i++){ if (cnt) cia.CNT = 1; cia.phi2 = 1; if (cnt) cia.CNT = 0; } }; cia.RS = 0x06; cia.DATA = 0x08; cia.RS = 0x07; cia.DATA = 0x00; cia.RS = 0x0F; // Force latch load into Timer B, // enable underflow reporting on Port B bit 7, // set Timer B to trigger on CNT, // and activate Timer B cia.DATA = 0x33; cia.RS = 0x06; // First, test a few ticks where CNT is not high. expect(cia.DATA).to.be.equal(0x08); // Validate inital timer value. tick(1); tick(1); tick(1); tick(1); expect(cia.DATA).to.be.equal(0x08); // Now verify CNT triggers! tick(1, true); expect(cia.DATA).to.be.equal(0x07); tick(1, true); expect(cia.DATA).to.be.equal(0x06); tick(1, true); tick(1, true); tick(1, true); tick(1, true); tick(1, true); tick(1, true); expect(cia.DATA).to.be.equal(0x00); tick(1, true); // Double check that timer has reset to latch value... expect(cia.DATA).to.be.equal(0x08); // Verify Interrupt (again... but it doesn't hurt!) cia.RS = 0x0D; let IC = cia.DATA; expect(IC & 0x02).to.be.equal(0x02); expect(IC & 0x80).to.be.equal(0x80); // Checking Port B bit 7 which should only go high for 1 cycle! cia.RS = 0x01; expect(cia.DATA & 0x80).to.be.equal(0x80); tick(1, true); expect(cia.DATA & 0x80).to.be.equal(0x00); // Check Port B bit 7 toggling... // ------------------------------------------- // Force latch load into Timer B, // enable underflow reporting on Port B bit 7, // setup Port B bit 7 to invert, // set Timer B to trigger on CNT, // and activate Timer B cia.RS = 0x0F; cia.DATA = 0x37; for (let i=0; i < 9; i++) tick(1, true); cia.RS = 0x06; expect(cia.DATA).to.be.equal(0x08); cia.RS = 0x01; expect(cia.DATA & 0x80).to.be.equal(0x80); tick(1, true); expect(cia.DATA & 0x80).to.be.equal(0x80); cia.RS = 0x06; expect(cia.DATA).to.be.equal(0x07); for (let i=0; i < 8; i++) tick(1, true); expect(cia.DATA).to.be.equal(0x08); cia.RS = 0x01; expect(cia.DATA & 0x80).to.be.equal(0x00); }); it("Timer B tick on Timer A Underflow (phi2 Triggered)", function(){ let cia = new MOSCIA(); let tick = (cycle) => { for (let i=0; i < cycle; i++) cia.phi2 = 1; }; // Setting up Timer A cia.RS = 0x04; cia.DATA = 0x02; cia.RS = 0x0E; cia.DATA = 0x11; // Setting up Timer B cia.RS = 0x06; cia.DATA = 0x01; cia.RS = 0x0F; cia.DATA = 0x51; cia.RS = 0x06; // Verify Timer B Low Byte Value expect(cia.DATA).to.be.equal(0x01); tick(1); // Timer B should not have changed. expect(cia.DATA).to.be.equal(0x01); // Timer A should have ticked down by 1. cia.RS = 0x04; expect(cia.DATA).to.be.equal(0x01); tick(1); tick(1); // Timer A should have underflowed and Timer B ticked down by 1 expect(cia.DATA).to.be.equal(0x02); cia.RS = 0x06; expect(cia.DATA).to.be.equal(0x00); // Check that Timer A triggered interrupt... but Timer B did not! cia.RS = 0x0D; let IC = cia.DATA; expect(IC & 0x01).to.be.equal(0x01); expect(IC & 0x02).to.be.equal(0x00); tick(3); // Run through Timer A once more. // Check that both Timer A & B triggered interrupt! IC = cia.DATA; expect(IC & 0x01).to.be.equal(0x01); expect(IC & 0x02).to.be.equal(0x02); // Both timers should have latched back to their starting values. cia.RS = 0x04; expect(cia.DATA).to.be.equal(0x02); cia.RS = 0x06; expect(cia.DATA).to.be.equal(0x01); }); it("Timer B ticks on Timer A Underflow (CNT Triggered for B only)", function(){ let cia = new MOSCIA(); let tick = (cycle) => { for (let i=0; i < cycle; i++) cia.phi2 = 1; }; // Setup Timer A (trigger on phi2 only) cia.RS = 0x04; cia.DATA = 0x02; cia.RS = 0x0E; cia.DATA = 0x11; // Setup Timer B (trigger on A underflow and CNT high) cia.RS = 0x06; cia.DATA = 0x01; cia.RS = 0x0F; cia.DATA = 0x71; // Keep CNT low, check that underflowing A *does NOT* tick B. cia.CNT = 0; // <-- Just to be sure. tick(2); // Just make sure we're ticking A cia.RS = 0x04; expect(cia.DATA).to.be.equal(0x00); tick(1); expect(cia.DATA).to.be.equal(0x02); // Make sure B did not tick! cia.RS = 0x06; expect(cia.DATA).to.be.equal(0x01); // Clear interrupt flags cia.RS = 0x0D; let IC = cia.DATA; // NOW tick with CNT high! cia.CNT = 1; tick(3); // Checking A rolled over cia.RS = 0x04; expect(cia.DATA).to.be.equal(0x02); // Now checking B ticked... cia.RS = 0x06; expect(cia.DATA).to.be.equal(0x00); // Double check CNT is high expect(cia.CNT).to.be.equal(1); // One more A cycle to check B rolls over! tick(3); expect(cia.DATA).to.be.equal(0x01); cia.RS = 0x04; expect(cia.DATA).to.be.equal(0x02); }); });