velxio/test/test_custom_chips/src/AVRHarness.js

122 lines
3.6 KiB
JavaScript

/**
* AVRHarness — Velxio-faithful avr8js wrapper for ATmega328P (Arduino Uno/Nano).
*
* Mirrors frontend/src/simulation/AVRSimulator.ts so chip integration tests
* exercise the same code paths Velxio uses in production.
*
* Exposes: load(hex), runCycles(n), getPin(pin), onPinChange(pin, cb),
* setAnalogVoltage(ch, volts), getPWMDuty(pin), pinManager, twi.
*/
import {
CPU, AVRIOPort, AVRTimer, AVRADC, AVRUSART, AVRTWI,
portBConfig, portCConfig, portDConfig,
timer0Config, timer1Config, timer2Config,
adcConfig, usart0Config, twiConfig,
avrInstruction,
} from 'avr8js';
import { parseIntelHex, bytesToProgramWords } from './intelHex.js';
import { PinManager } from './PinManager.js';
const PWM_PINS = [
{ ocrAddr: 0x47, pin: 6 },
{ ocrAddr: 0x48, pin: 5 },
{ ocrAddr: 0x88, pin: 9 },
{ ocrAddr: 0x8A, pin: 10 },
{ ocrAddr: 0xB3, pin: 11 },
{ ocrAddr: 0xB4, pin: 3 },
];
export class AVRHarness {
constructor() {
this.cpu = null;
this.ports = { B: null, C: null, D: null };
this.adc = null;
this.usart = null;
this.twi = null;
this.timers = [];
this.serialOut = [];
this.pinManager = new PinManager();
this.portValues = { B: 0, C: 0, D: 0 };
}
load(hexText) {
const bytes = parseIntelHex(hexText);
const program = bytesToProgramWords(bytes);
this._bindCpu(program);
}
loadProgram(words) {
const program = new Uint16Array(0x8000 / 2);
program.set(words);
this._bindCpu(program);
}
_bindCpu(program) {
this.cpu = new CPU(program, 8192);
this.ports.B = new AVRIOPort(this.cpu, portBConfig);
this.ports.C = new AVRIOPort(this.cpu, portCConfig);
this.ports.D = new AVRIOPort(this.cpu, portDConfig);
this.adc = new AVRADC(this.cpu, adcConfig);
this.twi = new AVRTWI(this.cpu, twiConfig, 16_000_000);
this.usart = new AVRUSART(this.cpu, usart0Config, 16_000_000);
this.usart.onByteTransmit = (v) => this.serialOut.push(String.fromCharCode(v));
this.timers = [
new AVRTimer(this.cpu, timer0Config),
new AVRTimer(this.cpu, timer1Config),
new AVRTimer(this.cpu, timer2Config),
];
for (const name of ['B', 'C', 'D']) {
const port = this.ports[name];
port.addListener((value) => {
const old = this.portValues[name];
this.portValues[name] = value;
this.pinManager.updatePort(`PORT${name}`, value, old);
});
}
}
runCycles(n) {
const end = this.cpu.cycles + n;
while (this.cpu.cycles < end) {
avrInstruction(this.cpu);
this.cpu.tick();
}
}
getPin(pin) {
// Bypass PinManager and read the AVRIOPort directly so we can see
// both input and output states (the simulator may drive pins both ways).
if (pin >= 0 && pin <= 7) {
return (this.ports.D.pinState(pin) === 3 || this.ports.D.pinState(pin) === 1) ? 1 : 0;
}
if (pin >= 8 && pin <= 13) {
const bit = pin - 8;
return (this.ports.B.pinState(bit) === 3 || this.ports.B.pinState(bit) === 1) ? 1 : 0;
}
if (pin >= 14 && pin <= 19) {
const bit = pin - 14;
return (this.ports.C.pinState(bit) === 3 || this.ports.C.pinState(bit) === 1) ? 1 : 0;
}
return 0;
}
onPinChange(pin, cb) {
return this.pinManager.onPinChange(pin, (_p, state) => cb(state ? 1 : 0));
}
setAnalogVoltage(channel, volts) {
if (this.adc) this.adc.channelValues[channel] = Math.max(0, Math.min(5, volts));
}
getPWMDuty(pin) {
const e = PWM_PINS.find((p) => p.pin === pin);
if (!e) return null;
return this.cpu.data[e.ocrAddr] / 255;
}
getSerialOutput() {
return this.serialOut.join('');
}
}