velxio/frontend/src/simulation/customChips/busLogic.ts

118 lines
4.2 KiB
TypeScript

/**
* 4-valued logic + drive-strength resolution for chip-to-chip bus nets.
* Phase 1 of the multi-chip digital bus track (project/multichip-bus/).
*
* A real wire is one of four states (the IEEE 4-state model): 0, 1, Z (high
* impedance / floating / tri-stated), X (unknown — typically two drivers
* fighting). Phase 0 gave every endpoint of a chip-to-chip net ONE shared key;
* this module turns the "release the bus by switching to VX_INPUT" idiom that
* rom-32k/ram-64k/8255 already use into REAL tri-state, so a multi-driver bus
* (many chips, one data line, only the enabled one drives) resolves correctly.
*
* Resolution is by (value, strength), not value alone (01-how-proteus-works.md
* section 3): strongest driver wins; equal strength + opposite values = X
* (contention); no real driver = Z (floating); a pull resistor is a `pull`
* driver that loses to `strong` but beats all-Z.
*
* Single-bit by design: a net is one wire. 3vl (packed 0/1/X vectors) is for
* multi-bit bus values and waveform display in a later phase; bit-level driver
* resolution needs Z + strength, which 3vl does not model, so it is a small
* self-contained fold here.
*/
export type V4 = '0' | '1' | 'Z' | 'X';
/** Verilog-style drive strengths — only the few that matter for digital buses. */
export enum Strength {
HIGHZ = 0, // not driving (a tri-stated / input pin contributes this)
PULL = 1, // pull-up / pull-down resistor, or INPUT_PULLUP/PULLDOWN
STRONG = 2, // a normal gate / chip output (the default for VX_OUTPUT)
SUPPLY = 3, // a hard power / ground rail
}
/** One driver's contribution to a net. `HIGHZ` strength means "not driving". */
export interface Drive {
value: 0 | 1;
strength: Strength;
}
export const HIGHZ_DRIVE: Drive = Object.freeze({ value: 0, strength: Strength.HIGHZ });
export interface Resolved {
v: V4;
/** Strength of the winning driver(s); HIGHZ when the net is floating (Z). */
strength: Strength;
}
/**
* Resolve a net from its drivers' contributions:
* - keep the drivers of maximum real strength;
* - all agree -> that value at that strength;
* - disagree -> X (contention) at that strength;
* - no real driver -> Z at HIGHZ.
*/
export function resolveNet(drives: Iterable<Drive>): Resolved {
let maxS: Strength = Strength.HIGHZ;
let val: 0 | 1 | null = null;
let contention = false;
for (const d of drives) {
if (d.strength <= Strength.HIGHZ) continue; // tri-stated: contributes nothing
if (d.strength > maxS) {
maxS = d.strength;
val = d.value;
contention = false;
} else if (d.strength === maxS) {
if (val === null) val = d.value;
else if (val !== d.value) contention = true;
}
}
if (val === null) return { v: 'Z', strength: Strength.HIGHZ };
if (contention) return { v: 'X', strength: maxS };
return { v: val === 1 ? '1' : '0', strength: maxS };
}
// Chip pin modes (mirror ChipInstance MODE_* / velxio-chip.h vx_pin_mode).
const VX_INPUT = 0;
const VX_OUTPUT = 1;
const VX_INPUT_PULLUP = 2;
const VX_INPUT_PULLDOWN = 3;
const VX_ANALOG = 4;
const VX_OUTPUT_LOW = 16;
const VX_OUTPUT_HIGH = 17;
/**
* Map a chip pin's mode + its last-written level to a bus Drive.
* OUTPUT / OUTPUT_LOW / OUTPUT_HIGH -> strong drive of `value`
* INPUT_PULLUP -> pull 1 ; INPUT_PULLDOWN -> pull 0
* INPUT / ANALOG -> Hi-Z (release the bus)
* This makes "switch to VX_INPUT" mean "stop driving" — real tri-state for free.
*/
export function modeToDrive(mode: number, value: 0 | 1): Drive {
switch (mode) {
case VX_OUTPUT:
case VX_OUTPUT_LOW:
case VX_OUTPUT_HIGH:
return { value, strength: Strength.STRONG };
case VX_INPUT_PULLUP:
return { value: 1, strength: Strength.PULL };
case VX_INPUT_PULLDOWN:
return { value: 0, strength: Strength.PULL };
case VX_INPUT:
case VX_ANALOG:
default:
return HIGHZ_DRIVE;
}
}
/**
* Project a resolved 4-value onto the boolean PinManager carries. A driven 1 is
* high; everything else (0, floating Z with no pull, contention X) reads low.
* Z and X are surfaced separately (see resolveNet) for warnings / diagnostics;
* callers that care about float-vs-low inspect the V4, not this boolean.
*/
export function resolvedToBool(r: Resolved): boolean {
return r.v === '1';
}