velxio/frontend/src/__tests__/spice-avr-mixed.test.ts

104 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Mixed-signal co-simulation tests: avr8js running alongside ngspice.
*
* These are the flagship demonstrations that Velxio can emulate real
* Arduino sketches interacting with real analog circuits. Ports the
* sandbox's spice_avr_mixed.test.js directly into the Velxio frontend.
*/
import { describe, it, expect } from 'vitest';
import { runNetlist } from './helpers/testSolver';
import { AVRTestHarness, adcReadProgram, potToPwmProgram } from './helpers/avrTestHarness';
describe('avr8js + ngspice mixed-signal', () => {
it('NTC divider (ngspice) → AVR ADC read recovers the voltage', { timeout: 60_000 }, async () => {
const points = [
{ TC: 0, Rntc: 33621, expectedADC: 789 },
{ TC: 25, Rntc: 10000, expectedADC: 511 },
{ TC: 50, Rntc: 3588, expectedADC: 270 },
];
for (const p of points) {
const { dcValue } = await runNetlist(`NTC divider @T=${p.TC}
Vcc vcc 0 DC 5
Rpull vcc a0 10k
Rntc a0 0 ${p.Rntc}
.op
.end`);
const va0 = dcValue('v(a0)');
const avr = new AVRTestHarness();
avr.loadProgram(adcReadProgram());
avr.setAnalogVoltage(0, va0);
avr.runCycles(500_000);
const ADCH = avr.reg(0x79);
const ADCL = avr.reg(0x78);
const raw = (ADCH << 2) | (ADCL >> 6);
expect(Math.abs(raw - p.expectedADC)).toBeLessThan(5);
}
});
it(
'AVR PWM (pot→PWM program) drives ngspice RC filter; settled DC matches duty × 5V',
{ timeout: 60_000 },
async () => {
const voltages = [1.0, 2.5, 4.0];
for (const vin of voltages) {
const avr = new AVRTestHarness();
avr.loadProgram(potToPwmProgram());
avr.setAnalogVoltage(0, vin);
avr.runCycles(200_000);
const duty = avr.getPWMDuty(6);
expect(duty).not.toBeNull();
expect(duty!).toBeGreaterThan(vin / 5 - 0.05);
expect(duty!).toBeLessThan(vin / 5 + 0.05);
const Vdc = duty! * 5;
const { dcValue } = await runNetlist(`PWM DC-equivalent to RC
Vpwm pwm 0 DC ${Vdc}
R1 pwm out 10k
Rload out 0 10Meg
.op
.end`);
const filtered = dcValue('v(out)');
expect(filtered).toBeGreaterThan(Vdc - 0.1);
expect(filtered).toBeLessThan(Vdc + 0.1);
}
},
);
it(
'co-sim loop: multiple ngspice .tran slices feed AVR ADC, pot movement is observable',
{ timeout: 60_000 },
async () => {
const avr = new AVRTestHarness();
avr.loadProgram(adcReadProgram());
async function stepBridge(wiperPos: number, sliceCount: number) {
for (let s = 0; s < sliceCount; s++) {
avr.runCycles(16_000); // 1 ms at 16 MHz
const Rtop = Math.max(1, (1 - wiperPos) * 10_000);
const Rbot = Math.max(1, wiperPos * 10_000);
const result = await runNetlist(`Pot divider
Vcc vcc 0 DC 5
Rtop vcc a0 ${Rtop}
Rbot a0 0 ${Rbot}
.tran 10u 1m
.end`);
const vEnd = (result.vec('v(a0)') as number[]).at(-1) ?? 0;
avr.setAnalogVoltage(0, vEnd);
}
}
await stepBridge(0.25, 5);
const raw1 = (avr.reg(0x79) << 2) | (avr.reg(0x78) >> 6);
await stepBridge(0.75, 5);
const raw2 = (avr.reg(0x79) << 2) | (avr.reg(0x78) >> 6);
expect(raw2).toBeGreaterThan(raw1);
expect(Math.abs(raw1 - 256)).toBeLessThan(30); // 0.25·1023
expect(Math.abs(raw2 - 767)).toBeLessThan(30); // 0.75·1023
},
);
});