velxio/test/test_custom_chips/test/chips/03_cd4094.test.js

114 lines
3.7 KiB
JavaScript

/**
* TIER 1 — Single chip: CD4094 8-stage shift register.
* 4 tests covering shift, strobe, power gate, and bit ordering.
*/
import { describe, it, expect } from 'vitest';
import { ChipInstance } from '../../src/ChipRuntime.js';
import { PinManager } from '../../src/PinManager.js';
import { loadChipWasm, chipWasmExists } from '../helpers.js';
const skip = !chipWasmExists('cd4094');
const PINS = {
VDD: 100, VSS: 101, CLK: 102, DATA: 103, STR: 104, OE: 105, QS: 106, QSN: 107,
Q1: 110, Q2: 111, Q3: 112, Q4: 113, Q5: 114, Q6: 115, Q7: 116, Q8: 117,
};
async function newCd4094() {
const pm = new PinManager();
const wires = new Map(Object.entries(PINS));
const chip = await ChipInstance.create({
wasm: loadChipWasm('cd4094'),
pinManager: pm,
wires,
});
chip.start();
// Power on.
pm.triggerPinChange(PINS.VDD, true);
pm.triggerPinChange(PINS.VSS, false);
return { pm, chip };
}
/** Shift one bit MSB-first by toggling DATA then pulsing CLK high then low. */
function shiftBit(pm, bit) {
pm.triggerPinChange(PINS.DATA, bit);
pm.triggerPinChange(PINS.CLK, true);
pm.triggerPinChange(PINS.CLK, false);
}
function strobe(pm) {
pm.triggerPinChange(PINS.STR, true);
pm.triggerPinChange(PINS.STR, false);
}
function readQ(pm) {
return [
pm.getPinState(PINS.Q1) ? 1 : 0,
pm.getPinState(PINS.Q2) ? 1 : 0,
pm.getPinState(PINS.Q3) ? 1 : 0,
pm.getPinState(PINS.Q4) ? 1 : 0,
pm.getPinState(PINS.Q5) ? 1 : 0,
pm.getPinState(PINS.Q6) ? 1 : 0,
pm.getPinState(PINS.Q7) ? 1 : 0,
pm.getPinState(PINS.Q8) ? 1 : 0,
];
}
describe('TIER 1 — CD4094 shift register', () => {
it.skipIf(skip)('shifts 8 bits MSB-first then strobes them to Q1..Q8', async () => {
const { pm, chip } = await newCd4094();
// Shift in 0b10110001 — high bit first reaches Q8 last (in MSB-first → Q1).
// The Wokwi/CD4094 convention used in cd4094.c writes bit position s->bit
// starting at 7 and going down; output Q[i] = (reg>>i)&1, so Q1 = bit 0.
// The first byte shifted in lands as bit 7 (Q8), so the order pushed is
// Q8, Q7, Q6, Q5, Q4, Q3, Q2, Q1.
const desiredQ = [1, 0, 0, 0, 1, 1, 0, 1]; // Q1..Q8 final state
// To get Q8=1, Q7=0, ..., Q1=1, push bits in order Q8..Q1 = [1,0,1,1,0,0,0,1]
const shiftOrder = [...desiredQ].reverse();
for (const b of shiftOrder) shiftBit(pm, b);
strobe(pm);
expect(readQ(pm)).toEqual(desiredQ);
chip.dispose();
});
it.skipIf(skip)('without VDD all outputs forced to LOW on next clock', async () => {
const { pm, chip } = await newCd4094();
// Shift in some pattern and strobe.
for (const b of [1, 1, 1, 1, 1, 1, 1, 1]) shiftBit(pm, b);
strobe(pm);
expect(readQ(pm).every((q) => q === 1)).toBe(true);
// Cut power and pulse CLK; chip forces outputs LOW.
pm.triggerPinChange(PINS.VDD, false);
pm.triggerPinChange(PINS.CLK, true);
expect(readQ(pm).every((q) => q === 0)).toBe(true);
chip.dispose();
});
it.skipIf(skip)('pre-strobe outputs stay LOW; only strobe latches them', async () => {
const { pm, chip } = await newCd4094();
for (const b of [1, 1, 1, 1, 1, 1, 1, 1]) shiftBit(pm, b);
expect(readQ(pm).every((q) => q === 0)).toBe(true);
strobe(pm);
expect(readQ(pm).every((q) => q === 1)).toBe(true);
chip.dispose();
});
it.skipIf(skip)('a second strobe latches the new shift register contents', async () => {
const { pm, chip } = await newCd4094();
for (const b of [1, 1, 1, 1, 1, 1, 1, 1]) shiftBit(pm, b);
strobe(pm);
expect(readQ(pm)).toEqual([1, 1, 1, 1, 1, 1, 1, 1]);
for (const b of [0, 0, 0, 0, 0, 0, 0, 0]) shiftBit(pm, b);
strobe(pm);
expect(readQ(pm)).toEqual([0, 0, 0, 0, 0, 0, 0, 0]);
chip.dispose();
});
});