2016-08-30 20:55:00 +02:00
|
|
|
namespace pxsim {
|
2016-09-09 10:23:39 +02:00
|
|
|
const GROUND_COLOR = "blue";
|
|
|
|
const POWER_COLOR = "red";
|
|
|
|
|
|
|
|
export interface AllocatorOpts {
|
2016-08-30 20:55:00 +02:00
|
|
|
boardDef: BoardDefinition,
|
2016-09-09 10:23:39 +02:00
|
|
|
partDefs: Map<PartDefinition>,
|
|
|
|
partsList: string[]
|
2016-08-30 20:55:00 +02:00
|
|
|
fnArgs: any,
|
2016-09-09 10:23:39 +02:00
|
|
|
// Used for finding the nearest available power pins
|
|
|
|
getBBCoord: (loc: BBLoc) => visuals.Coord,
|
2016-08-30 20:55:00 +02:00
|
|
|
};
|
|
|
|
export interface AllocatorResult {
|
2016-09-09 10:23:39 +02:00
|
|
|
partsAndWires: PartAndWiresInst[],
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
export interface PartInst {
|
2016-08-30 20:55:00 +02:00
|
|
|
name: string,
|
2016-09-09 10:23:39 +02:00
|
|
|
simulationBehavior?: string,
|
|
|
|
visual: PartVisualDefinition,
|
|
|
|
bbFit: PartBBFit,
|
|
|
|
startColumnIdx: number,
|
|
|
|
startRowIdx: number,
|
|
|
|
breadboardConnections: BBLoc[],
|
|
|
|
params: Map<string>,
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
|
|
|
export interface WireInst {
|
|
|
|
start: Loc,
|
|
|
|
end: Loc,
|
|
|
|
color: string,
|
|
|
|
};
|
2016-09-09 10:23:39 +02:00
|
|
|
export interface AssemblyStep {
|
|
|
|
part?: boolean,
|
|
|
|
wireIndices?: number[],
|
|
|
|
}
|
|
|
|
export interface PartAndWiresInst {
|
|
|
|
part?: PartInst,
|
|
|
|
wires?: WireInst[],
|
|
|
|
assembly: AssemblyStep[],
|
|
|
|
}
|
|
|
|
export interface PartBBFit {
|
|
|
|
xOffset: number,
|
|
|
|
yOffset: number,
|
|
|
|
rowCount: number,
|
|
|
|
colCount: number,
|
|
|
|
}
|
|
|
|
interface PinBBFit {
|
|
|
|
partRelativeColIdx: number,
|
|
|
|
partRelativeRowIdx: number,
|
|
|
|
xOffset: number,
|
|
|
|
yOffset: number,
|
|
|
|
}
|
|
|
|
interface PinIR {
|
|
|
|
loc: XY,
|
|
|
|
def: PartPinDefinition,
|
|
|
|
target: PinTarget,
|
|
|
|
bbFit: PinBBFit,
|
|
|
|
}
|
|
|
|
interface PartIR {
|
2016-08-30 20:55:00 +02:00
|
|
|
name: string,
|
2016-08-31 06:15:27 +02:00
|
|
|
def: PartDefinition,
|
2016-09-09 10:23:39 +02:00
|
|
|
partParams: Map<string>,
|
|
|
|
pins: PinIR[],
|
|
|
|
bbFit: PartBBFit,
|
2016-08-30 20:55:00 +02:00
|
|
|
};
|
2016-09-09 10:23:39 +02:00
|
|
|
interface PartPlacement extends PartIR {
|
|
|
|
startColumnIdx: number,
|
|
|
|
startRowIdx: number,
|
|
|
|
};
|
|
|
|
type WireIRLoc = PinTarget | BBLoc;
|
|
|
|
interface WireIR {
|
|
|
|
pinIdx: number,
|
|
|
|
start: WireIRLoc,
|
|
|
|
end: WireIRLoc,
|
|
|
|
color: string,
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
interface PartIRAndWireIRs extends PartPlacement {
|
|
|
|
wires: WireIR[],
|
|
|
|
};
|
2016-09-03 00:24:16 +02:00
|
|
|
interface PowerUsage {
|
|
|
|
topGround: boolean,
|
|
|
|
topThreeVolt: boolean,
|
|
|
|
bottomGround: boolean,
|
|
|
|
bottomThreeVolt: boolean,
|
2016-09-03 01:11:58 +02:00
|
|
|
singleGround: boolean,
|
|
|
|
singleThreeVolt: boolean,
|
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
interface AllocLocOpts {
|
|
|
|
referenceBBPin?: BBLoc,
|
|
|
|
};
|
|
|
|
interface AllocWireOpts {
|
|
|
|
//TODO: port
|
|
|
|
startColumn: number,
|
|
|
|
partGPIOPins: string[],
|
|
|
|
}
|
|
|
|
function isOnBreadboardBottom(location: WireIRLoc) {
|
2016-09-03 01:11:58 +02:00
|
|
|
let isBot = false;
|
2016-09-09 10:23:39 +02:00
|
|
|
if (typeof location !== "string" && (<BBLoc>location).type === "breadboard") {
|
|
|
|
let bbLoc = <BBLoc>location;
|
|
|
|
let row = bbLoc.row;
|
2016-09-03 01:11:58 +02:00
|
|
|
isBot = 0 <= ["a", "b", "c", "d", "e"].indexOf(row);
|
|
|
|
}
|
|
|
|
return isBot;
|
|
|
|
}
|
|
|
|
const arrCount = (a: boolean[]) => a.reduce((p, n) => p + (n ? 1 : 0), 0);
|
|
|
|
const arrAny = (a: boolean[]) => arrCount(a) > 0;
|
2016-09-09 10:23:39 +02:00
|
|
|
function computePowerUsage(wire: WireIR): PowerUsage {
|
|
|
|
let ends = [wire.start, wire.end];
|
2016-09-03 01:11:58 +02:00
|
|
|
let endIsGround = ends.map(e => e === "ground");
|
|
|
|
let endIsThreeVolt = ends.map(e => e === "threeVolt");
|
|
|
|
let endIsBot = ends.map(e => isOnBreadboardBottom(e));
|
|
|
|
let hasGround = arrAny(endIsGround);
|
|
|
|
let hasThreeVolt = arrAny(endIsThreeVolt);
|
|
|
|
let hasBot = arrAny(endIsBot);
|
|
|
|
return {
|
|
|
|
topGround: hasGround && !hasBot,
|
|
|
|
topThreeVolt: hasThreeVolt && !hasBot,
|
|
|
|
bottomGround: hasGround && hasBot,
|
|
|
|
bottomThreeVolt: hasThreeVolt && hasBot,
|
|
|
|
singleGround: hasGround,
|
|
|
|
singleThreeVolt: hasThreeVolt
|
|
|
|
};
|
|
|
|
}
|
|
|
|
function mergePowerUsage(powerUsages: PowerUsage[]) {
|
|
|
|
let finalPowerUsage = powerUsages.reduce((p, n) => ({
|
|
|
|
topGround: p.topGround || n.topGround,
|
|
|
|
topThreeVolt: p.topThreeVolt || n.topThreeVolt,
|
|
|
|
bottomGround: p.bottomGround || n.bottomGround,
|
|
|
|
bottomThreeVolt: p.bottomThreeVolt || n.bottomThreeVolt,
|
|
|
|
singleGround: n.singleGround ? p.singleGround === null : p.singleGround,
|
|
|
|
singleThreeVolt: n.singleThreeVolt ? p.singleThreeVolt === null : p.singleThreeVolt,
|
|
|
|
}), {
|
|
|
|
topGround: false,
|
|
|
|
topThreeVolt: false,
|
|
|
|
bottomGround: false,
|
|
|
|
bottomThreeVolt: false,
|
|
|
|
singleGround: null,
|
|
|
|
singleThreeVolt: null,
|
|
|
|
});
|
|
|
|
if (finalPowerUsage.singleGround)
|
|
|
|
finalPowerUsage.topGround = finalPowerUsage.bottomGround = false;
|
|
|
|
if (finalPowerUsage.singleThreeVolt)
|
|
|
|
finalPowerUsage.topThreeVolt = finalPowerUsage.bottomThreeVolt = false;
|
|
|
|
return finalPowerUsage;
|
2016-09-03 00:24:16 +02:00
|
|
|
}
|
2016-08-30 20:55:00 +02:00
|
|
|
function copyDoubleArray(a: string[][]) {
|
|
|
|
return a.map(b => b.map(p => p));
|
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
function merge2<A, B>(a: A, b: B): A & B {
|
|
|
|
let res: any = {};
|
|
|
|
for (let aKey in a)
|
|
|
|
res[aKey] = (<any>a)[aKey];
|
|
|
|
for (let bKey in b)
|
|
|
|
res[bKey] = (<any>b)[bKey];
|
|
|
|
return <A & B>res;
|
|
|
|
}
|
|
|
|
function merge3<A, B, C>(a: A, b: B, c: C): A & B & C {
|
|
|
|
return merge2(merge2(a, b), c);
|
|
|
|
}
|
|
|
|
function readPin(arg: string): MicrobitPin {
|
2016-08-30 20:55:00 +02:00
|
|
|
U.assert(!!arg, "Invalid pin: " + arg);
|
|
|
|
let pin = arg.split("DigitalPin.")[1];
|
2016-09-09 10:23:39 +02:00
|
|
|
return <MicrobitPin>pin;
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
|
|
|
function mkReverseMap(map: {[key: string]: string}) {
|
|
|
|
let origKeys: string[] = [];
|
|
|
|
let origVals: string[] = [];
|
|
|
|
for (let key in map) {
|
|
|
|
origKeys.push(key);
|
|
|
|
origVals.push(map[key]);
|
|
|
|
}
|
|
|
|
let newMap: {[key: string]: string} = {};
|
|
|
|
for (let i = 0; i < origKeys.length; i++) {
|
|
|
|
let newKey = origVals[i];
|
|
|
|
let newVal = origKeys[i];
|
|
|
|
newMap[newKey] = newVal;
|
|
|
|
}
|
|
|
|
return newMap;
|
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
function isConnectedToBB(pin: PartPinDefinition): boolean {
|
|
|
|
return pin.orientation === "-Z" && pin.style === "male";
|
|
|
|
}
|
2016-08-30 20:55:00 +02:00
|
|
|
class Allocator {
|
2016-09-09 10:23:39 +02:00
|
|
|
//TODO: better handling of allocation errors
|
2016-08-30 20:55:00 +02:00
|
|
|
private opts: AllocatorOpts;
|
|
|
|
private availablePowerPins = {
|
|
|
|
top: {
|
2016-09-09 10:23:39 +02:00
|
|
|
threeVolt: mkRange(26, 51).map(n => <BBLoc>{type: "breadboard", row: "+", col: `${n}`}),
|
|
|
|
ground: mkRange(26, 51).map(n => <BBLoc>{type: "breadboard", row: "-", col: `${n}`}),
|
2016-08-30 20:55:00 +02:00
|
|
|
},
|
|
|
|
bottom: {
|
2016-09-09 10:23:39 +02:00
|
|
|
threeVolt: mkRange(1, 26).map(n => <BBLoc>{type: "breadboard", row: "+", col: `${n}`}),
|
|
|
|
ground: mkRange(1, 26).map(n => <BBLoc>{type: "breadboard", row: "-", col: `${n}`}),
|
2016-08-30 20:55:00 +02:00
|
|
|
},
|
|
|
|
};
|
2016-09-03 01:11:58 +02:00
|
|
|
private powerUsage: PowerUsage;
|
2016-09-09 10:23:39 +02:00
|
|
|
private availableWireColors: string[];
|
2016-08-30 20:55:00 +02:00
|
|
|
|
|
|
|
constructor(opts: AllocatorOpts) {
|
|
|
|
this.opts = opts;
|
|
|
|
}
|
|
|
|
|
2016-09-09 10:23:39 +02:00
|
|
|
private allocPartIRs(def: PartDefinition, name: string, bbFit: PartBBFit): PartIR[] {
|
|
|
|
let partIRs: PartIR[] = [];
|
|
|
|
let mkIR = (def: PartDefinition, name: string, instPins?: PinTarget[], partParams?: Map<string>): PartIR => {
|
|
|
|
let pinIRs: PinIR[] = [];
|
|
|
|
for (let i = 0; i < def.numberOfPins; i++) {
|
|
|
|
let pinDef = def.pinDefinitions[i];
|
|
|
|
let pinTarget: PinTarget;
|
|
|
|
if (typeof pinDef.target === "string") {
|
|
|
|
pinTarget = <PinTarget>pinDef.target;
|
|
|
|
} else {
|
|
|
|
let instIdx = (<PinInstantiationIdx>pinDef.target).pinInstantiationIdx;
|
|
|
|
U.assert(!!instPins && instPins[instIdx] !== undefined,
|
|
|
|
`No pin found for PinInstantiationIdx: ${instIdx}. (Is the part missing an ArguementRole or "trackArgs=" annotations?)`);
|
|
|
|
pinTarget = instPins[instIdx];
|
|
|
|
}
|
|
|
|
let pinLoc = def.visual.pinLocations[i];
|
|
|
|
let adjustedY = bbFit.yOffset + pinLoc.y;
|
|
|
|
let relativeRowIdx = Math.round(adjustedY / def.visual.pinDistance);
|
|
|
|
let relativeYOffset = adjustedY - relativeRowIdx * def.visual.pinDistance;
|
|
|
|
let adjustedX = bbFit.xOffset + pinLoc.x;
|
|
|
|
let relativeColIdx = Math.round(adjustedX / def.visual.pinDistance);
|
|
|
|
let relativeXOffset = adjustedX - relativeColIdx * def.visual.pinDistance;
|
|
|
|
let pinBBFit: PinBBFit = {
|
|
|
|
partRelativeRowIdx: relativeRowIdx,
|
|
|
|
partRelativeColIdx: relativeColIdx,
|
|
|
|
xOffset: relativeXOffset,
|
|
|
|
yOffset: relativeYOffset
|
|
|
|
};
|
|
|
|
pinIRs.push({
|
|
|
|
def: pinDef,
|
|
|
|
loc: pinLoc,
|
|
|
|
target: pinTarget,
|
|
|
|
bbFit: pinBBFit,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
name: name,
|
|
|
|
def: def,
|
|
|
|
pins: pinIRs,
|
|
|
|
partParams: partParams || {},
|
|
|
|
bbFit: bbFit
|
|
|
|
};
|
|
|
|
};
|
|
|
|
if (def.instantiation.kind === "singleton") {
|
|
|
|
partIRs.push(mkIR(def, name));
|
|
|
|
} else if (def.instantiation.kind === "function") {
|
|
|
|
let fnAlloc = def.instantiation as PartFunctionDefinition;
|
|
|
|
let fnNm = fnAlloc.fullyQualifiedName;
|
|
|
|
let callsitesTrackedArgs = <string[]>this.opts.fnArgs[fnNm];
|
|
|
|
U.assert(!!callsitesTrackedArgs && !!callsitesTrackedArgs.length, "Failed to read pin(s) from callsite for: " + fnNm);
|
|
|
|
callsitesTrackedArgs.forEach(fnArgsStr => {
|
|
|
|
let fnArgsSplit = fnArgsStr.split(",");
|
|
|
|
U.assert(fnArgsSplit.length === fnAlloc.argumentRoles.length,
|
|
|
|
`Mismatch between number of arguments at callsite (function name: ${fnNm}) vs number of argument roles in part definition (part: ${name}).`);
|
|
|
|
let instPins: PinTarget[] = [];
|
|
|
|
let paramArgs: Map<string> = {};
|
|
|
|
fnArgsSplit.forEach((arg, idx) => {
|
|
|
|
let role = fnAlloc.argumentRoles[idx];
|
|
|
|
if (role.partParameter !== undefined) {
|
|
|
|
paramArgs[role.partParameter] = arg;
|
|
|
|
}
|
|
|
|
if (role.pinInstantiationIdx !== undefined) {
|
|
|
|
let instIdx = role.pinInstantiationIdx;
|
|
|
|
let pin = readPin(arg);
|
|
|
|
instPins[instIdx] = pin;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
partIRs.push(mkIR(def, name, instPins, paramArgs));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return partIRs;
|
|
|
|
}
|
|
|
|
private computePartDimensions(def: PartDefinition, name: string): PartBBFit {
|
|
|
|
let pinLocs = def.visual.pinLocations;
|
|
|
|
let pinDefs = def.pinDefinitions;
|
|
|
|
let numPins = def.numberOfPins;
|
|
|
|
U.assert(pinLocs.length === numPins, `Mismatch between "numberOfPins" and length of "visual.pinLocations" for "${name}"`);
|
|
|
|
U.assert(pinDefs.length === numPins, `Mismatch between "numberOfPins" and length of "pinDefinitions" for "${name}"`);
|
|
|
|
U.assert(numPins > 0, `Part "${name}" has no pins`);
|
|
|
|
let pins = pinLocs.map((loc, idx) => merge3({idx: idx}, loc, pinDefs[idx]));
|
|
|
|
let bbPins = pins.filter(p => p.orientation === "-Z");
|
|
|
|
let hasBBPins = bbPins.length > 0;
|
|
|
|
let pinDist = def.visual.pinDistance;
|
|
|
|
let xOff: number;
|
|
|
|
let yOff: number;
|
|
|
|
let colCount: number;
|
|
|
|
let rowCount: number;
|
|
|
|
if (hasBBPins) {
|
|
|
|
let refPin = bbPins[0];
|
|
|
|
let refPinColIdx = Math.ceil(refPin.x / pinDist);
|
|
|
|
let refPinRowIdx = Math.ceil(refPin.y / pinDist);
|
|
|
|
xOff = refPinColIdx * pinDist - refPin.x;
|
|
|
|
yOff = refPinRowIdx * pinDist - refPin.y;
|
|
|
|
colCount = Math.ceil((xOff + def.visual.width) / pinDist) + 1;
|
|
|
|
rowCount = Math.ceil((yOff + def.visual.height) / pinDist) + 1;
|
|
|
|
} else {
|
|
|
|
colCount = Math.ceil(def.visual.width / pinDist);
|
|
|
|
rowCount = Math.ceil(def.visual.height / pinDist);
|
|
|
|
xOff = colCount * pinDist - def.visual.width;
|
|
|
|
yOff = rowCount * pinDist - def.visual.height;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
xOffset: xOff,
|
|
|
|
yOffset: yOff,
|
|
|
|
rowCount: rowCount,
|
|
|
|
colCount: colCount
|
|
|
|
};
|
|
|
|
}
|
|
|
|
private allocColumns(colCounts: {colCount: number}[]): number[] {
|
|
|
|
let partsCount = colCounts.length;
|
|
|
|
const totalColumnsCount = visuals.BREADBOARD_MID_COLS; //TODO allow multiple breadboards
|
|
|
|
let totalSpaceNeeded = colCounts.map(d => d.colCount).reduce((p, n) => p + n, 0);
|
|
|
|
let extraSpace = totalColumnsCount - totalSpaceNeeded;
|
|
|
|
if (extraSpace <= 0) {
|
|
|
|
console.log("Not enough breadboard space!");
|
|
|
|
//TODO
|
|
|
|
}
|
|
|
|
let padding = Math.floor(extraSpace / (partsCount - 1 + 2));
|
|
|
|
let partSpacing = padding; //Math.floor(extraSpace/(partsCount-1));
|
|
|
|
let totalPartPadding = extraSpace - partSpacing * (partsCount - 1);
|
|
|
|
let leftPadding = Math.floor(totalPartPadding / 2);
|
|
|
|
let rightPadding = Math.ceil(totalPartPadding / 2);
|
|
|
|
let nextAvailableCol = 1 + leftPadding;
|
|
|
|
let partStartCol = colCounts.map(part => {
|
|
|
|
let col = nextAvailableCol;
|
|
|
|
nextAvailableCol += part.colCount + partSpacing;
|
|
|
|
return col;
|
|
|
|
});
|
|
|
|
return partStartCol;
|
|
|
|
}
|
|
|
|
private placeParts(parts: PartIR[]): PartPlacement[] {
|
|
|
|
const totalRowsCount = visuals.BREADBOARD_MID_ROWS + 2; // 10 letters + 2 for the middle gap
|
|
|
|
let startColumnIndices = this.allocColumns(parts.map(p => p.bbFit));
|
|
|
|
let startRowIndicies = parts.map(p => {
|
|
|
|
let extraRows = totalRowsCount - p.bbFit.rowCount;
|
|
|
|
let topPad = Math.floor(extraRows / 2);
|
|
|
|
let startIdx = topPad;
|
|
|
|
if (startIdx > 4)
|
|
|
|
startIdx = 4;
|
|
|
|
if (startIdx < 1)
|
|
|
|
startIdx = 1;
|
|
|
|
return startIdx;
|
|
|
|
});
|
|
|
|
let placements = parts.map((p, idx) => {
|
|
|
|
let row = startRowIndicies[idx];
|
|
|
|
let col = startColumnIndices[idx];
|
|
|
|
return merge2({startColumnIdx: col, startRowIdx: row}, p);
|
|
|
|
});
|
|
|
|
return placements;
|
|
|
|
}
|
|
|
|
private nextColor(): string {
|
|
|
|
if (!this.availableWireColors || this.availableWireColors.length <= 0) {
|
|
|
|
this.availableWireColors = visuals.GPIO_WIRE_COLORS.map(c => c);
|
|
|
|
}
|
|
|
|
return this.availableWireColors.pop();
|
|
|
|
}
|
|
|
|
private allocWireIRs(part: PartPlacement): PartIRAndWireIRs {
|
|
|
|
let groupToColor: string[] = [];
|
|
|
|
let wires: WireIR[] = part.pins.map((pin, pinIdx) => {
|
|
|
|
let end = pin.target;
|
|
|
|
let start: WireIRLoc;
|
|
|
|
let colIdx = part.startColumnIdx + pin.bbFit.partRelativeColIdx;
|
|
|
|
let colName = visuals.getColumnName(colIdx);
|
|
|
|
let pinRowIdx = part.startRowIdx + pin.bbFit.partRelativeRowIdx;
|
|
|
|
if (pinRowIdx >= 7) //account for middle gap
|
|
|
|
pinRowIdx -= 2;
|
|
|
|
if (isConnectedToBB(pin.def)) {
|
|
|
|
//make a wire from bb top or bottom to target
|
|
|
|
let connectedToTop = pinRowIdx < 5;
|
|
|
|
let rowName = connectedToTop ? "j" : "a";
|
|
|
|
start = {
|
|
|
|
type: "breadboard",
|
|
|
|
row: rowName,
|
|
|
|
col: colName,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
//make a wire directly from pin to target
|
|
|
|
let rowName = visuals.getRowName(pinRowIdx);
|
|
|
|
start = {
|
|
|
|
type: "breadboard",
|
|
|
|
row: rowName,
|
|
|
|
col: colName,
|
|
|
|
xOffset: pin.bbFit.xOffset / part.def.visual.pinDistance,
|
|
|
|
yOffset: pin.bbFit.yOffset / part.def.visual.pinDistance
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let color: string;
|
|
|
|
if (end === "ground") {
|
|
|
|
color = GROUND_COLOR;
|
|
|
|
} else if (end === "threeVolt") {
|
|
|
|
color = POWER_COLOR;
|
|
|
|
} else if (typeof pin.def.colorGroup === "number") {
|
|
|
|
if (groupToColor[pin.def.colorGroup]) {
|
|
|
|
color = groupToColor[pin.def.colorGroup];
|
|
|
|
} else {
|
|
|
|
color = groupToColor[pin.def.colorGroup] = this.nextColor();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
color = this.nextColor()
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
start: start,
|
|
|
|
end: end,
|
|
|
|
color: color,
|
|
|
|
pinIdx: pinIdx,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return merge2(part, {wires: wires});
|
|
|
|
}
|
|
|
|
private allocLocation(location: WireIRLoc, opts: AllocLocOpts): Loc {
|
2016-08-30 20:55:00 +02:00
|
|
|
if (location === "ground" || location === "threeVolt") {
|
2016-09-03 01:11:58 +02:00
|
|
|
//special case if there is only a single ground or three volt pin in the whole build
|
|
|
|
if (location === "ground" && this.powerUsage.singleGround) {
|
|
|
|
let boardGroundPin = this.getBoardGroundPin();
|
|
|
|
return {type: "dalboard", pin: boardGroundPin};
|
|
|
|
} else if (location === "threeVolt" && this.powerUsage.singleThreeVolt) {
|
|
|
|
let boardThreeVoltPin = this.getBoardThreeVoltPin();
|
|
|
|
return {type: "dalboard", pin: boardThreeVoltPin};
|
|
|
|
}
|
|
|
|
|
2016-09-09 10:23:39 +02:00
|
|
|
U.assert(!!opts.referenceBBPin);
|
|
|
|
let nearestCoord = this.opts.getBBCoord(opts.referenceBBPin);
|
2016-08-30 20:55:00 +02:00
|
|
|
let firstTopAndBot = [
|
|
|
|
this.availablePowerPins.top.ground[0] || this.availablePowerPins.top.threeVolt[0],
|
|
|
|
this.availablePowerPins.bottom.ground[0] || this.availablePowerPins.bottom.threeVolt[0]
|
|
|
|
].map(loc => {
|
|
|
|
return this.opts.getBBCoord(loc);
|
|
|
|
});
|
|
|
|
if (!firstTopAndBot[0] || !firstTopAndBot[1]) {
|
|
|
|
console.debug(`No more available "${location}" locations!`);
|
|
|
|
//TODO
|
|
|
|
}
|
|
|
|
let nearTop = visuals.findClosestCoordIdx(nearestCoord, firstTopAndBot) == 0;
|
2016-09-09 10:23:39 +02:00
|
|
|
let barPins: BBLoc[];
|
2016-08-30 20:55:00 +02:00
|
|
|
if (nearTop) {
|
|
|
|
if (location === "ground") {
|
2016-09-03 00:24:16 +02:00
|
|
|
barPins = this.availablePowerPins.top.ground;
|
2016-08-30 20:55:00 +02:00
|
|
|
} else if (location === "threeVolt") {
|
2016-09-03 00:24:16 +02:00
|
|
|
barPins = this.availablePowerPins.top.threeVolt;
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (location === "ground") {
|
2016-09-03 00:24:16 +02:00
|
|
|
barPins = this.availablePowerPins.bottom.ground;
|
2016-08-30 20:55:00 +02:00
|
|
|
} else if (location === "threeVolt") {
|
2016-09-03 00:24:16 +02:00
|
|
|
barPins = this.availablePowerPins.bottom.threeVolt;
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
|
|
|
}
|
2016-09-03 00:24:16 +02:00
|
|
|
let pinCoords = barPins.map(rowCol => {
|
2016-08-30 20:55:00 +02:00
|
|
|
return this.opts.getBBCoord(rowCol);
|
|
|
|
});
|
2016-09-03 00:24:16 +02:00
|
|
|
let closestPinIdx = visuals.findClosestCoordIdx(nearestCoord, pinCoords);
|
|
|
|
let pin = barPins[closestPinIdx];
|
2016-08-30 20:55:00 +02:00
|
|
|
if (nearTop) {
|
2016-09-03 00:24:16 +02:00
|
|
|
this.availablePowerPins.top.ground.splice(closestPinIdx, 1);
|
|
|
|
this.availablePowerPins.top.threeVolt.splice(closestPinIdx, 1);
|
2016-08-30 20:55:00 +02:00
|
|
|
} else {
|
2016-09-03 00:24:16 +02:00
|
|
|
this.availablePowerPins.bottom.ground.splice(closestPinIdx, 1);
|
|
|
|
this.availablePowerPins.bottom.threeVolt.splice(closestPinIdx, 1);
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
return pin;
|
|
|
|
} else if ((<BBLoc>location).type === "breadboard") {
|
|
|
|
return <BBLoc>location;
|
2016-09-01 06:53:48 +02:00
|
|
|
} else if (location === "MOSI" || location === "MISO" || location === "SCK") {
|
|
|
|
if (!this.opts.boardDef.spiPins)
|
|
|
|
console.debug("No SPI pin mappings found!");
|
|
|
|
let pin = (<any>this.opts.boardDef.spiPins)[location as string] as string;
|
|
|
|
return {type: "dalboard", pin: pin};
|
|
|
|
} else if (location === "SDA" || location === "SCL") {
|
|
|
|
if (!this.opts.boardDef.i2cPins)
|
|
|
|
console.debug("No I2C pin mappings found!");
|
|
|
|
let pin = (<any>this.opts.boardDef.i2cPins)[location as string] as string;
|
|
|
|
return {type: "dalboard", pin: pin};
|
2016-08-30 20:55:00 +02:00
|
|
|
} else {
|
2016-09-09 10:23:39 +02:00
|
|
|
//it must be a MicrobitPin
|
|
|
|
U.assert(typeof location === "string", "Unknown location type: " + location);
|
|
|
|
let mbPin = <MicrobitPin>location;
|
|
|
|
let boardPin = this.opts.boardDef.gpioPinMap[mbPin];
|
|
|
|
U.assert(!!boardPin, "Unknown pin: " + location);
|
|
|
|
return {type: "dalboard", pin: boardPin};
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
private getBoardGroundPin(): string {
|
2016-08-30 20:55:00 +02:00
|
|
|
let boardGround = this.opts.boardDef.groundPins[0] || null;
|
|
|
|
if (!boardGround) {
|
|
|
|
console.log("No available ground pin on board!");
|
|
|
|
//TODO
|
|
|
|
}
|
2016-09-03 01:11:58 +02:00
|
|
|
return boardGround;
|
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
private getBoardThreeVoltPin(): string {
|
2016-08-30 20:55:00 +02:00
|
|
|
let threeVoltPin = this.opts.boardDef.threeVoltPins[0] || null;
|
|
|
|
if (!threeVoltPin) {
|
|
|
|
console.log("No available 3.3V pin on board!");
|
|
|
|
//TODO
|
|
|
|
}
|
2016-09-03 01:11:58 +02:00
|
|
|
return threeVoltPin;
|
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
private allocPowerWires(powerUsage: PowerUsage): PartAndWiresInst {
|
2016-09-03 01:11:58 +02:00
|
|
|
let boardGroundPin = this.getBoardGroundPin();
|
|
|
|
let threeVoltPin = this.getBoardThreeVoltPin();
|
2016-09-09 10:23:39 +02:00
|
|
|
const topLeft: BBLoc = {type: "breadboard", row: "-", col: "26"};
|
|
|
|
const botLeft: BBLoc = {type: "breadboard", row: "-", col: "1"};
|
|
|
|
const topRight: BBLoc = {type: "breadboard", row: "-", col: "50"};
|
|
|
|
const botRight: BBLoc = {type: "breadboard", row: "-", col: "25"};
|
|
|
|
let top: BBLoc, bot: BBLoc;
|
2016-08-30 20:55:00 +02:00
|
|
|
if (this.opts.boardDef.attachPowerOnRight) {
|
|
|
|
top = topRight;
|
|
|
|
bot = botRight;
|
|
|
|
} else {
|
|
|
|
top = topLeft;
|
|
|
|
bot = botLeft;
|
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
let groundWires: WireInst[] = [];
|
|
|
|
let threeVoltWires: WireInst[] = [];
|
2016-09-03 00:24:16 +02:00
|
|
|
if (powerUsage.bottomGround && powerUsage.topGround) {
|
|
|
|
//bb top - <==> bb bot -
|
2016-09-09 10:23:39 +02:00
|
|
|
groundWires.push({
|
|
|
|
start: this.allocLocation("ground", {referenceBBPin: top}),
|
|
|
|
end: this.allocLocation("ground", {referenceBBPin: bot}),
|
|
|
|
color: GROUND_COLOR,
|
2016-09-03 00:24:16 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
if (powerUsage.topGround) {
|
|
|
|
//board - <==> bb top -
|
2016-09-09 10:23:39 +02:00
|
|
|
groundWires.push({
|
|
|
|
start: this.allocLocation("ground", {referenceBBPin: top}),
|
2016-09-03 01:11:58 +02:00
|
|
|
end: {type: "dalboard", pin: boardGroundPin},
|
2016-09-09 10:23:39 +02:00
|
|
|
color: GROUND_COLOR,
|
2016-09-03 00:24:16 +02:00
|
|
|
});
|
|
|
|
} else if (powerUsage.bottomGround) {
|
|
|
|
//board - <==> bb bot -
|
2016-09-09 10:23:39 +02:00
|
|
|
groundWires.push({
|
|
|
|
start: this.allocLocation("ground", {referenceBBPin: bot}),
|
2016-09-03 01:11:58 +02:00
|
|
|
end: {type: "dalboard", pin: boardGroundPin},
|
2016-09-09 10:23:39 +02:00
|
|
|
color: GROUND_COLOR,
|
2016-09-03 00:24:16 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
if (powerUsage.bottomThreeVolt && powerUsage.bottomGround) {
|
|
|
|
//bb top + <==> bb bot +
|
2016-09-09 10:23:39 +02:00
|
|
|
threeVoltWires.push({
|
|
|
|
start: this.allocLocation("threeVolt", {referenceBBPin: top}),
|
|
|
|
end: this.allocLocation("threeVolt", {referenceBBPin: bot}),
|
|
|
|
color: POWER_COLOR,
|
2016-09-03 00:24:16 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
if (powerUsage.topThreeVolt) {
|
|
|
|
//board + <==> bb top +
|
2016-09-09 10:23:39 +02:00
|
|
|
threeVoltWires.push({
|
|
|
|
start: this.allocLocation("threeVolt", {referenceBBPin: top}),
|
2016-09-03 00:24:16 +02:00
|
|
|
end: {type: "dalboard", pin: threeVoltPin},
|
2016-09-09 10:23:39 +02:00
|
|
|
color: POWER_COLOR,
|
2016-09-03 00:24:16 +02:00
|
|
|
});
|
|
|
|
} else if (powerUsage.bottomThreeVolt) {
|
|
|
|
//board + <==> bb bot +
|
2016-09-09 10:23:39 +02:00
|
|
|
threeVoltWires.push({
|
|
|
|
start: this.allocLocation("threeVolt", {referenceBBPin: bot}),
|
2016-09-03 00:24:16 +02:00
|
|
|
end: {type: "dalboard", pin: threeVoltPin},
|
2016-09-09 10:23:39 +02:00
|
|
|
color: POWER_COLOR,
|
2016-09-03 00:24:16 +02:00
|
|
|
});
|
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
let assembly: AssemblyStep[] = [];
|
|
|
|
if (groundWires.length > 0)
|
|
|
|
assembly.push({wireIndices: groundWires.map((w, i) => i)});
|
|
|
|
let numGroundWires = groundWires.length;
|
|
|
|
if (threeVoltWires.length > 0)
|
|
|
|
assembly.push({wireIndices: threeVoltWires.map((w, i) => i + numGroundWires)});
|
|
|
|
return {
|
|
|
|
wires: groundWires.concat(threeVoltWires),
|
|
|
|
assembly: assembly
|
|
|
|
};
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
private allocWire(wireIR: WireIR): WireInst {
|
|
|
|
let ends = [wireIR.start, wireIR.end];
|
2016-08-30 20:55:00 +02:00
|
|
|
let endIsPower = ends.map(e => e === "ground" || e === "threeVolt");
|
2016-09-03 00:24:16 +02:00
|
|
|
//allocate non-power first so we know the nearest pin for the power end
|
2016-09-09 10:23:39 +02:00
|
|
|
let endInsts = ends.map((e, idx) => !endIsPower[idx] ? this.allocLocation(e, {}) : null)
|
2016-09-03 00:24:16 +02:00
|
|
|
//allocate power pins closest to the other end of the wire
|
2016-08-30 20:55:00 +02:00
|
|
|
endInsts = endInsts.map((e, idx) => {
|
|
|
|
if (e)
|
|
|
|
return e;
|
2016-09-03 00:24:16 +02:00
|
|
|
let locInst = <BBLoc>endInsts[1 - idx]; // non-power end
|
2016-09-09 10:23:39 +02:00
|
|
|
let l = this.allocLocation(ends[idx], {
|
|
|
|
referenceBBPin: locInst,
|
2016-08-30 20:55:00 +02:00
|
|
|
});
|
|
|
|
return l;
|
|
|
|
});
|
2016-09-09 10:23:39 +02:00
|
|
|
return {start: endInsts[0], end: endInsts[1], color: wireIR.color};
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
private allocPart(ir: PartPlacement): PartInst {
|
|
|
|
let bbConnections = ir.pins
|
|
|
|
.filter(p => isConnectedToBB(p.def))
|
|
|
|
.map(p => {
|
|
|
|
let rowIdx = ir.startRowIdx + p.bbFit.partRelativeRowIdx;
|
|
|
|
if (rowIdx >= 7) //account for middle gap
|
|
|
|
rowIdx -= 2;
|
|
|
|
let rowName = visuals.getRowName(rowIdx);
|
|
|
|
let colIdx = ir.startColumnIdx + p.bbFit.partRelativeColIdx;
|
|
|
|
let colName = visuals.getColumnName(colIdx);
|
|
|
|
return <BBLoc>{
|
|
|
|
type: "breadboard",
|
|
|
|
row: rowName,
|
|
|
|
col: colName,
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
});
|
|
|
|
let part: PartInst = {
|
|
|
|
name: ir.name,
|
|
|
|
visual: ir.def.visual,
|
|
|
|
bbFit: ir.bbFit,
|
|
|
|
startColumnIdx: ir.startColumnIdx,
|
|
|
|
startRowIdx: ir.startRowIdx,
|
|
|
|
breadboardConnections: bbConnections,
|
|
|
|
params: ir.partParams,
|
|
|
|
simulationBehavior: ir.def.simulationBehavior
|
|
|
|
}
|
|
|
|
return part;
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
public allocAll(): AllocatorResult {
|
|
|
|
let partNmAndDefs = this.opts.partsList
|
|
|
|
.map(partName => {return {name: partName, def: this.opts.partDefs[partName]}})
|
|
|
|
.filter(d => !!d.def);
|
|
|
|
if (partNmAndDefs.length > 0) {
|
|
|
|
let partNmsList = partNmAndDefs.map(p => p.name);
|
|
|
|
let partDefsList = partNmAndDefs.map(p => p.def);
|
|
|
|
let dimensions = partNmAndDefs.map(nmAndPart => this.computePartDimensions(nmAndPart.def, nmAndPart.name));
|
|
|
|
let partIRs: PartIR[] = [];
|
|
|
|
partNmAndDefs.forEach((nmAndDef, idx) => {
|
|
|
|
let dims = dimensions[idx];
|
|
|
|
let irs = this.allocPartIRs(nmAndDef.def, nmAndDef.name, dims);
|
|
|
|
partIRs = partIRs.concat(irs);
|
|
|
|
})
|
|
|
|
let partPlacements = this.placeParts(partIRs);
|
|
|
|
let partsAndWireIRs = partPlacements.map(p => this.allocWireIRs(p));
|
|
|
|
let allWireIRs = partsAndWireIRs.map(p => p.wires).reduce((p, n) => p.concat(n), []);
|
|
|
|
let allPowerUsage = allWireIRs.map(w => computePowerUsage(w));
|
|
|
|
this.powerUsage = mergePowerUsage(allPowerUsage);
|
|
|
|
let basicWires = this.allocPowerWires(this.powerUsage);
|
|
|
|
let partsAndWires: PartAndWiresInst[] = partsAndWireIRs.map((irs, idx) => {
|
|
|
|
let part = this.allocPart(irs);
|
|
|
|
let wires = irs.wires.map(w => this.allocWire(w));
|
|
|
|
let pinIdxToWireIdx: number[] = [];
|
|
|
|
irs.wires.forEach((wIR, idx) => {
|
|
|
|
pinIdxToWireIdx[wIR.pinIdx] = idx;
|
|
|
|
});
|
|
|
|
let assembly: AssemblyStep[] = irs.def.assembly.map(stepDef => {
|
|
|
|
return {
|
|
|
|
part: stepDef.part,
|
|
|
|
wireIndices: (stepDef.pinIndices || []).map(i => pinIdxToWireIdx[i])
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
});
|
|
|
|
return {
|
|
|
|
part: part,
|
|
|
|
wires: wires,
|
|
|
|
assembly: assembly
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
});
|
|
|
|
let all = [basicWires].concat(partsAndWires);
|
|
|
|
return {
|
|
|
|
partsAndWires: all
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
2016-09-09 10:23:39 +02:00
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
partsAndWires: []
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function allocateDefinitions(opts: AllocatorOpts): AllocatorResult {
|
2016-09-09 10:23:39 +02:00
|
|
|
return new Allocator(opts).allocAll();
|
2016-08-30 20:55:00 +02:00
|
|
|
}
|
|
|
|
}
|