All files / src/web via-registers.js

100% Statements 46/46
100% Branches 6/6
100% Functions 30/30
100% Lines 41/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198    4x   4x                   4x 4x                   16x 4x 32x 4x                                                           1471x 1471x                                     2080x       130x 130x               130x 130x               65x                 65x                   259x       65x 64x         20x 5x                               5x     65x 65x 65x 65x 65x 65x                   5x 5x             65x 65x   65x         65x     65x         4x     65x 65x 65x 65x      
import { lit, namedBits, noMeaning, register, spelt } from "./decoded-registers.js";
 
const IerReadsSet = 0x80;
 
const ControlLineModes = [
    "falling edge",
    "falling edge, independent",
    "rising edge",
    "rising edge, independent",
    "handshake output",
    "pulse output",
    "low output",
    "high output",
];
const Timer1Modes = ["one-shot", "continuous", "one-shot, PB7 pulse", "continuous, PB7 square wave"];
const ShiftRegisterModes = [
    "off",
    "in by T2",
    "in by clock",
    "in by CB1",
    "out free-running by T2",
    "out by T2",
    "out by clock",
    "out by CB1",
];
const Timer1Forms = Timer1Modes.map((mode) => `T1 ${mode}`);
const Timer2Modes = ["T2 counts PB6 pulses", "T2 one-shot"];
const ShiftRegisterForms = ShiftRegisterModes.map((mode) => `SR ${mode}`);
const CountForms = ["65535 cycles", "65535 pulses"];
 
/**
 * @typedef {object} ControlLine what a VIA's control line is wired to on this machine
 * @property {string} name CA1, CA2, CB1 or CB2
 * @property {string} wiring a word or two, shown beside the name
 * @property {string} title what it is wired to, in full
 */
 
/**
 * @typedef {object} ViaWiring
 * @property {ControlLine} ca1
 * @property {ControlLine} ca2
 * @property {ControlLine} cb1
 * @property {ControlLine} cb2
 * @property {string} t1 what timer 1 reaching zero means here
 */
 
/**
 * @typedef {object} PortLines what a port's lines are wired to
 * @property {string} title
 * @property {(pins: number, state: object) => import("./decoded-registers.js").Meaning[]} decode
 */
 
/**
 * The registers every VIA has, as the decoded panels show them. Reading changes nothing the machine sees: the timers
 * are caught up as {@link Via#peekTimers} does, which is also what brings the interrupt flags up to date.
 * @param {import("../via.js").Via} via
 */
export function viaState(via) {
    const { t1c, t1l, t2c, t2l } = via.peekTimers();
    return {
        ora: via.ora,
        ddra: via.ddra,
        portA: via.portapins,
        orb: via.orb,
        ddrb: via.ddrb,
        portB: via.portbpins,
        pcr: via.pcr,
        acr: via.acr,
        ifr: via.ifr,
        ier: via.ier | IerReadsSet,
        t1c,
        t1l,
        t2c,
        t2l,
    };
}
 
function controlLineForms(line) {
    return ControlLineModes.map((mode) => `${line.name} ${line.wiring}: ${mode}`);
}
 
function edgeLine(line, risingBit) {
    const edge = risingBit ? "rising edge" : "falling edge";
    return spelt(
        `${line.name} ${line.wiring}: ${edge}`,
        `${line.name}, ${line.title}: interrupts on a ${edge}`,
        controlLineForms(line),
    );
}
 
function controlLine(line, mode) {
    const what = ControlLineModes[mode];
    return spelt(
        `${line.name} ${line.wiring}: ${what}`,
        `${line.name}, ${line.title}: ${what}`,
        controlLineForms(line),
    );
}
 
function decodePcr(pcr, { ca1, ca2, cb1, cb2 }) {
    return [
        edgeLine(ca1, pcr & 1),
        controlLine(ca2, (pcr >>> 1) & 7),
        edgeLine(cb1, pcr & 0x10),
        controlLine(cb2, (pcr >>> 5) & 7),
    ];
}
 
function decodeAcr(acr) {
    return [
        spelt(Timer1Forms[acr >>> 6], "Bits 6 and 7: timer 1's mode", Timer1Forms),
        spelt(Timer2Modes[acr & 0x20 ? 0 : 1], "Bit 5: timer 2's mode", Timer2Modes),
        spelt(ShiftRegisterForms[(acr >>> 2) & 7], "Bits 2 to 4: the shift register's mode", ShiftRegisterForms),
        lit("PB latch", "Bit 1: port B latches its input on CB1's active edge", acr & 2),
        lit("PA latch", "Bit 0: port A latches its input on CA1's active edge", acr & 1),
    ];
}
 
function inCycles(value) {
    return [spelt(`${value} cycles`, "In cycles: the VIA counts at 1 MHz", CountForms)];
}
 
function decodeT2Counter(value, { acr }) {
    if (acr & 0x20) return [spelt(`${value} pulses`, "Falling edges on PB6 still to count", CountForms)];
    return inCycles(value);
}
 
/** By bit number in the IFR and IER. */
function interruptSources({ ca1, ca2, cb1, cb2, t1 }) {
    const line = ({ name, wiring, title }) => ({ name: `${name} ${wiring}`, title: `${name}: ${title}` });
    return [
        line(ca2),
        line(ca1),
        { name: "SR", title: "The shift register has shifted eight bits" },
        line(cb2),
        line(cb1),
        { name: "T2", title: "Timer 2 has reached zero" },
        { name: "T1", title: t1 },
    ];
}
 
/**
 * The output registers, data directions and lines of both ports.
 * @param {{a: PortLines, b: PortLines}} ports
 */
export function portsSection({ a, b }) {
    return {
        heading: "Ports",
        registers: [
            register("ORA", "Output register A", 8, (s) => s.ora, noMeaning),
            register("DDRA", "Port A's data direction: a set bit drives its line", 8, (s) => s.ddra, noMeaning),
            register("PA", a.title, 8, (s) => s.portA, a.decode),
            register("ORB", "Output register B", 8, (s) => s.orb, noMeaning),
            register("DDRB", "Port B's data direction: a set bit drives its line", 8, (s) => s.ddrb, noMeaning),
            register("PB", b.title, 8, (s) => s.portB, b.decode),
        ],
    };
}
 
/**
 * The PCR and ACR, and the interrupts flagged and enabled, named by what this machine wires to them.
 * @param {ViaWiring} wiring
 */
export function controlSection(wiring) {
    const sources = interruptSources(wiring);
    return {
        heading: "Control",
        registers: [
            register(
                "PCR",
                "Peripheral control: CA1, CA2, CB1 and CB2",
                8,
                (s) => s.pcr,
                (pcr) => decodePcr(pcr, wiring),
            ),
            register("ACR", "Auxiliary control: the timers, shift register and latches", 8, (s) => s.acr, decodeAcr),
            register(
                "IFR",
                "Interrupt flags",
                8,
                (s) => s.ifr,
                namedBits([...sources, { name: "IRQ", title: "Some enabled flag is set" }]),
            ),
            register("IER", "Interrupts enabled", 8, (s) => s.ier, namedBits(sources)),
        ],
    };
}
 
export const TimersSection = {
    heading: "Timers",
    registers: [
        register("T1C", "Timer 1's counter, as a program reads it", 16, (s) => s.t1c, inCycles),
        register("T1L", "Timer 1's latch", 16, (s) => s.t1l, inCycles),
        register("T2C", "Timer 2's counter, as a program reads it", 16, (s) => s.t2c, decodeT2Counter),
        register("T2L", "Timer 2's latch, as last written", 16, (s) => s.t2l, inCycles),
    ],
};