114 lines
3.7 KiB
JavaScript
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();
|
|
});
|
|
});
|