RosemaryUserImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Barth, September 17, 1987 1:34:15 pm PDT
Bertrand Serlet October 17, 1986 10:07:27 pm PDT
Last Edited by: Gasbarro October 14, 1987 10:27:09 am PDT
Christian Le Cocq June 5, 1987 3:03:24 pm PDT
Jean-Marc Frailong December 8, 1987 1:46:12 pm PST
DIRECTORY Atom, Basics, BasicTime, Buttons, ChoiceButtons, Containers, Convert, Core, CoreClasses, CoreFlat, CoreOps, CoreProperties, FS, RefTab, IO, List, PlotGraph, Ports, Process, ProcessProps, Rope, ReadEvalPrint, Real, RedBlackTree, Rosemary, RosemaryUser, Rules, SymTab, ViewerClasses, ViewerEvents, ViewerIO, ViewerOps, ViewerTools;
RosemaryUserImpl: CEDAR MONITOR
IMPORTS Basics, BasicTime, Buttons, ChoiceButtons, Containers, Convert, CoreClasses, CoreFlat, CoreOps, CoreProperties, FS, RefTab, IO, List, PlotGraph, Ports, Process, ProcessProps, Rope, ReadEvalPrint, Real, RedBlackTree, Rosemary, Rules, SymTab, ViewerEvents, ViewerIO, ViewerOps, ViewerTools
EXPORTS RosemaryUser
= BEGIN OPEN RosemaryUser;
Management of tester viewer
entryHeight: NAT = 15; -- how tall to make each line of items
entryVSpace: NAT = 4;  -- vertical leading space between lines
pointsPerInch: NAT = 72;  -- horizontal space for text ropes
borderOffset: NAT = 2;
Column: TYPE = NAT [0..3);
ColumnStart: ARRAY Column OF NAT = [entryVSpace, 1*pointsPerInch+entryVSpace, 4*pointsPerInch+entryVSpace];
testProcTable: SymTab.Ref ← SymTab.Create[];
rosemaryHandle: ATOM ← $RosemaryUserRoseDisplay; -- CellType property holding RoseDisplay
DisplayViewer: PUBLIC PROC [simulation: Rosemary.Simulation, cellType: Core.CellType, name: ROPENIL, displayWires: CoreFlat.FlatWires ← NIL, graphWires: CoreFlat.FlatWires ← NIL] RETURNS [handle: RoseDisplay] = {
viewer: ViewerClasses.Viewer← Containers.Create[[  
name: name,
iconic: FALSE,
column: left,
scrollable: FALSE ]];
handle ← NEW[RoseDisplayRec];
handle.simulation ← simulation;
handle.cellType ← cellType;
MakeDisplayAndBrowse[displayWires: displayWires, graphWires: graphWires, height: entryVSpace, handle: handle, viewer: viewer, name: name];
};
TestProcedureViewer: PUBLIC PROC [cellType: Core.CellType, testButtons: LIST OF ROPE, name: ROPENIL, displayWires: CoreFlat.FlatWires ← NIL, graphWires: CoreFlat.FlatWires ← NIL, cutSet: CoreFlat.CutSet ← NIL, historySize: NAT ← 0, steady: BOOLFALSE, recordDeltas: BOOLTRUE] RETURNS [tester: Tester] = {
The RoseDisplay is put as a CellType property and a tester viewer property to ease up data structure destrcution when the viewer is destroyed.
viewer: ViewerClasses.Viewer; -- created after simulation has been expanded
height: CARDINAL ← entryVSpace;
tester ← NEW[TesterRec];
tester.display ← NEW[RoseDisplayRec];
tester.steadyInit ← steady;
tester.recordDeltas ← recordDeltas;
tester.display.cutSet ← cutSet;
tester.display.cellType ← cellType;
tester.intermediatePort ← Ports.CreatePort[cellType, TRUE];
tester.display.simulation ← Rosemary.Instantiate[cellType, tester.intermediatePort, cutSet, historySize];
IF historySize>0 THEN {
tester.historySize ← historySize;
tester.testVectorBuffer ← NEW[Rosemary.PortSequenceRec[historySize]];
FOR b: NAT IN [0..historySize) DO
tester.testVectorBuffer[b] ← Ports.CreatePort[cellType, TRUE];
ENDLOOP;
};
viewer ← Containers.Create[[name: name, iconic: FALSE, column: left, scrollable: FALSE]];
CoreProperties.PutCellTypeProp[cellType, rosemaryHandle, tester.display]; -- for retrieval
ViewerOps.AddProp[viewer, rosemaryHandle, tester.display]; -- for retrieval
[] ← ViewerEvents.RegisterEventProc[proc: TesterDestroyed, event: destroy, filter: viewer, before: TRUE]; -- call proc just before tester viewer is destroyed
[] ← Buttons.Create[info: [name: "Start Test",
wx: ColumnStart[0], wy: height,
ww: 0, wh: entryHeight,
parent: viewer, border: TRUE], clientData: tester, proc: ButtonStartTest];
tester.runState ← ChoiceButtons.BuildTextPrompt[viewer: viewer, x: ColumnStart[1], y: height-borderOffset, title: "Run State:", textViewerWidth: 2*pointsPerInch].textViewer;
ViewerTools.InhibitUserEdits[tester.runState];
ViewerTools.SetContents[tester.runState, "Idle"];
IF historySize>0 THEN tester.historyTrigger ← ChoiceButtons.BuildTextPrompt[viewer: viewer, x: ColumnStart[2], y: height-borderOffset, title: "History Trigger:", textViewerWidth: 2*pointsPerInch].textViewer;
height ← height + entryHeight + entryVSpace;
[] ← Buttons.Create[info: [name: "Interrupt", wx: ColumnStart[0], wy: height, ww: 0, wh: entryHeight, parent: viewer, border: TRUE], clientData: tester, proc: Interrupt];
tester.evalsSinceStart ← ChoiceButtons.BuildTextPrompt[viewer: viewer, x: ColumnStart[1], y: height-borderOffset, title: "Evals Since Start:", textViewerWidth: 1*pointsPerInch].textViewer;
ViewerTools.InhibitUserEdits[tester.evalsSinceStart];
IF historySize>0 THEN [] ← Buttons.Create[info: [name: "Next State", wx: ColumnStart[2], wy: height, ww: 0, wh: entryHeight, parent: viewer, border: TRUE], clientData: tester, proc: NextState];
height ← height + entryHeight + entryVSpace;
[] ← Buttons.Create[info: [name: "Proceed", wx: ColumnStart[0], wy: height, ww: 0, wh: entryHeight, parent: viewer, border: TRUE], clientData: tester, proc: Proceed];
tester.proceedUntil ← ChoiceButtons.BuildTextPrompt[viewer: viewer, x: ColumnStart[1], y: height-borderOffset, title: "Proceed Until:", textViewerWidth: 1*pointsPerInch].textViewer;
IF historySize>0 THEN [] ← Buttons.Create[info: [name: "Previous State", wx: ColumnStart[2], wy: height, ww: 0, wh: entryHeight, parent: viewer, border: TRUE], clientData: tester, proc: PreviousState];
height ← height + entryHeight + entryVSpace;
[] ← Buttons.Create[info: [name: "Abort", wx: ColumnStart[0], wy: height, ww: 0, wh: entryHeight, parent: viewer, border: TRUE], clientData: tester, proc: Abort, guarded: TRUE];
[] ← Buttons.Create[info: [name: "Single Eval", wx: ColumnStart[1]+borderOffset, wy: height, ww: 0, wh: entryHeight, parent: viewer, border: TRUE], clientData: tester, proc: SingleEval];
height ← height + entryHeight + entryVSpace;
Containers.ChildXBound[viewer, Rules.Create[[parent: viewer, wy: height, ww: viewer.cw, wh: 2]]];
height ← height + entryVSpace;
IF testButtons.rest#NIL THEN {
Column: TYPE = NAT [0..3);
ColumnStart: ARRAY Column OF NAT = [entryVSpace, 2*pointsPerInch+entryVSpace, 4*pointsPerInch+entryVSpace];
column: Column ← 0;
first: Buttons.Button ← NIL;
FOR tbs: LIST OF ROPE ← testButtons, tbs.rest UNTIL tbs=NIL DO
this: Buttons.Button ← Buttons.Create[info: [name: tbs.first,
wx: ColumnStart[column], wy: height,
ww: 0, wh: entryHeight,
parent: viewer, border: TRUE], clientData: tester, proc: TestButtonProc];
tester.testButtonList ← CONS[this, tester.testButtonList];
IF first=NIL THEN first ← this;
IF column=LAST[Column] THEN {
column ← 0;
height ← height + entryHeight + entryVSpace;
}
ELSE column ← column+1;
ENDLOOP;
IF column>0 THEN height ← height + entryHeight + entryVSpace;
Buttons.SetDisplayStyle[first, $BlackOnGrey];
Containers.ChildXBound[viewer, Rules.Create[[parent: viewer, wy: height, ww: viewer.cw, wh: 2]]];
height ← height + entryVSpace;
};
tester.currentTestProc ← testButtons.first;
MakeDisplayAndBrowse[displayWires, graphWires, height, tester.display, viewer, name];
};
MakeDisplayAndBrowse: PROC [displayWires: CoreFlat.FlatWires, graphWires: CoreFlat.FlatWires, height: CARDINAL, handle: RoseDisplay, viewer: ViewerClasses.Viewer, name: ROPE] = {
handle.wDir ← ProcessProps.GetProp[$WorkingDirectory];
{
rep: ReadEvalPrint.Handle;
[handle.tsin, handle.tsout] ← ViewerIO.CreateViewerStreams[name: Rope.Cat[name, " Rosemary Script"]];
rep ← ReadEvalPrint.CreateStreamEvaluator[clientProc: ExploreDesign, prompt: "%l> %l", in: handle.tsin, out: handle.tsout, topLevel: TRUE];
rep.clientData ← handle;
ReadEvalPrint.MainLoop[h: rep, properties: NIL];
};
IF displayWires#NIL THEN {
Column: TYPE = NAT [0..2);
ColumnStart: ARRAY Column OF NAT = [entryVSpace, 3*pointsPerInch+pointsPerInch/4+entryVSpace];
column: Column ← 0;
count: INT ← 0;
FOR dws: CoreFlat.FlatWires ← displayWires, dws.rest UNTIL dws=NIL DO
count ← count + 1;
ENDLOOP;
IF count>100 THEN IO.PutF[handle.tsout, "You really don't want to display %g wires, maybe you should try again", IO.int[count]]
ELSE FOR dws: CoreFlat.FlatWires ← displayWires, dws.rest UNTIL dws=NIL DO
dn: ROPE ← CoreFlat.WirePathRope[handle.cellType, dws.first^];
IF Rope.Equal[Rope.Substr[dn, 0, 7], "public."] THEN dn ← Rope.Substr[dn, 7];
handle.displayWires ← CONS[ChoiceButtons.BuildTextPrompt[viewer: viewer, x: ColumnStart[column], y: height-borderOffset, title: dn, textViewerWidth: 2*pointsPerInch, clientdata: dws.first], handle.displayWires];
ViewerTools.InhibitUserEdits[handle.displayWires.first.textViewer];
IF column=LAST[Column] THEN {
column ← 0;
height ← height + entryHeight + entryVSpace;
}
ELSE column ← column+1;
ENDLOOP;
IF column>0 THEN height ← height + entryHeight + entryVSpace;
Containers.ChildXBound[viewer, Rules.Create[[parent: viewer, wy: height, ww: viewer.cw, wh: 2]]];
height ← height + entryVSpace;
};
[] ← Buttons.Create[info: [name: "Update", wx: ColumnStart[0], wy: height, ww: 0, wh: entryHeight, parent: viewer, border: TRUE], clientData: handle, proc: UpdateDisplayButton];
[] ← Buttons.Create[info: [name: "Log", wx: ColumnStart[1], wy: height, ww: 0, wh: entryHeight, parent: viewer, border: TRUE], clientData: handle, proc: LogChanges];
height ← height + entryHeight + entryVSpace;
handle.path ← ChoiceButtons.BuildTextPrompt[viewer: viewer, x: ColumnStart[0], y: height-borderOffset, title: "Instantiation Path:", textViewerWidth: 4*pointsPerInch].textViewer;
height ← height + entryHeight + entryVSpace;
handle.currentWire ← ChoiceButtons.BuildTextPrompt[viewer: viewer, x: ColumnStart[0], y: height-borderOffset, title: "Current Wire:", textViewerWidth: 4*pointsPerInch].textViewer;
height ← height + entryHeight + entryVSpace;
Containers.ChildXBound[viewer, Rules.Create[[parent: viewer, wy: height, ww: viewer.cw, wh: 2]]];
ViewerOps.SetOpenHeight[viewer, height];
ViewerOps.PaintViewer[viewer, all];
handle.name ← name;
FOR wires: CoreFlat.FlatWires ← graphWires, wires.rest UNTIL wires=NIL DO
IF AddWireToPlot[handle, wires.first]#NIL THEN ERROR;
ENDLOOP;
};
LogChanges: Buttons.ButtonProc = {
h: RoseDisplay ← NARROW[clientData];
IF (h.logChanges ← ~h.logChanges) THEN {
Buttons.SetDisplayStyle[NARROW[parent], $WhiteOnBlack];
}
ELSE Buttons.SetDisplayStyle[NARROW[parent], $BlackOnWhite];
};
RegisterTestProc: PUBLIC PROC [name: ROPE, proc: TestProc] = {
[] ← SymTab.Store[x: testProcTable, key: name, val: NEW[TestProc ← proc]];
};
TestButtonProc: Buttons.ButtonProc = {
h: Tester ← NARROW[clientData];
selectedButton: ViewerClasses.Viewer ← NARROW[parent];
FOR l: LIST OF Buttons.Button ← h.testButtonList, l.rest WHILE l#NIL DO
Buttons.SetDisplayStyle[l.first, IF l.first = selectedButton THEN $BlackOnGrey ELSE $BlackOnWhite];
ENDLOOP;
h.currentTestProc ← selectedButton.name;
};
AbortSignal: SIGNAL = CODE;
LogSettle: PUBLIC PROC [handle: RoseDisplay, time: INT, memory: BOOLTRUE] = {
UpdateWire: Rosemary.UpdateProc = {
coreWire: CoreFlat.FlatWireRec;
RecordDelta[handle, roseWire, time];
IF NOT handle.logChanges THEN RETURN;
UpdateDisplay[handle];
coreWire ← roseWire.wire;
wireKey^ ← coreWire;
IO.PutF[handle.tsout, "%g←%g ", IO.rope[CoreFlat.WirePathRope[handle.cellType, coreWire]], IO.rope[Ports.LevelSequenceToRope[Rosemary.WireValue[ handle.simulation, wireKey]]]];
};
wireKey: CoreFlat.FlatWire ← NEW[CoreFlat.FlatWireRec];
Rosemary.Settle[handle.simulation, UpdateWire, memory];
DeltaFinished[handle, time];
IF handle.logChanges THEN IO.PutRope[handle.tsout, "\n\n"];
};
ButtonStartTest: Buttons.ButtonProc = {
h: Tester ← NARROW[clientData];
StartTest[h];
};
StartTest: PUBLIC PROC [tester: Tester] = {
GrumbleStartTest: PROC = {
IF NOT AlreadyStarted[tester] THEN DoStartTest[tester];
};
ProcessProps.AddPropList[LIST[NEW[Atom.DottedPairNode ← [key: $WorkingDirectory, val: tester.display.wDir]]], GrumbleStartTest]
};
DoStartTest: PROC [h: Tester] = {
Eval: PROC [memory: BOOLTRUE, clockEval: BOOLFALSE, checkPorts: BOOLTRUE] = {
UpdateWire: Rosemary.UpdateProc = {
coreWire: CoreFlat.FlatWireRec;
IF h.abort THEN SIGNAL AbortSignal;
IF h.recordDeltas THEN RecordDelta[h.display, roseWire, h.evalSinceStartCount+1];
IF h.display.logChanges THEN {
UpdateDisplay[h.display];
coreWire ← roseWire.wire;
wireKey^ ← coreWire;
IO.PutF[h.display.tsout, "%g←%g ", IO.rope[CoreFlat.WirePathRope[h.display.cellType, coreWire]], IO.rope[Ports.LevelSequenceToRope[Rosemary.WireValue[ h.display.simulation, wireKey]]]];
};
};
DO
inBuf: BOOL ← h.displayedStatePoint#h.currentStatePoint AND h.validStates>0;
testPort: Ports.Port ← IF inBuf THEN h.testVectorBuffer[NextBuf[h, h.displayedStatePoint]] ELSE currentTestPort;
Ports.CopyPortValue[from: testPort, to: h.intermediatePort];
Rosemary.Settle[h.display.simulation, UpdateWire, memory, clockEval];
IF h.display.logChanges THEN IO.PutRope[h.display.tsout, "\n\n"];
UpdateESSC[h, h.evalSinceStartCount+1];
DeltaFinished[h.display, h.evalSinceStartCount];
IF h.evalSinceStartCount>=h.historyTriggerCount AND NOT inBuf AND h.historySize>0 THEN {
h.currentStatePoint ← NextBuf[h, h.currentStatePoint];
h.displayedStatePoint ← h.currentStatePoint;
IF h.validStates < h.historySize THEN h.validStates ← h.validStates + 1;
Rosemary.StatePoint[h.display.simulation, h.currentStatePoint];
Ports.CopyPortValue[from: currentTestPort, to: h.testVectorBuffer[h.currentStatePoint]];
};
IF NOT clockEval AND checkPorts THEN Ports.CheckPortValue[root: h.display.cellType.public, truth: testPort, question: h.intermediatePort ! Ports.CheckError => UpdateDisplay[h.display]];
IF EvalsFinished[h] THEN SIGNAL AbortSignal;
IF inBuf THEN {
h.displayedStatePoint ← NextBuf[h, h.displayedStatePoint];
IF h.displayedStatePoint=h.currentStatePoint THEN EXIT;
check new state same as recorded state;
}
ELSE EXIT;
ENDLOOP;
};
wireKey: CoreFlat.FlatWire ← NEW[CoreFlat.FlatWireRec];
testProc: TestProc ← NARROW[SymTab.Fetch[x: testProcTable, key: h.currentTestProc].val, REF TestProc]^;
currentTestPort: Ports.Port ← Ports.CreatePort[h.display.cellType, TRUE];
time: BasicTime.GMT ← BasicTime.nullGMT;
IF testProc=NIL THEN ERROR;
UpdateESSC[h, 0];
h.validStates ← 0;
SetEvalUntil[h: h, interrupt: FALSE, abort: FALSE];
ViewerTools.SetContents[h.runState, "Initializing"];
time ← BasicTime.Now[];
Rosemary.Initialize[simulation: h.display.simulation, steady: h.steadyInit ! UNWIND => FinishedTest[h]];
InitializeDeltas[h.display];
Rosemary.PrintPeriod["Rosemary initialized in", time, BasicTime.Now[]];
ViewerTools.SetContents[h.runState, "Running"];
time ← BasicTime.Now[];
testProc[h.display.cellType, currentTestPort, Eval ! Rosemary.Stop => IF reason = $BoolWireHasX THEN {IO.PutF[h.display.tsout, "\nWire %g settled to an X and has boolean ports", IO.rope[CoreFlat.WirePathRope[h.display.cellType, NARROW[data, CoreFlat.FlatWire]^]]]; RESUME}; AbortSignal => CONTINUE; UNWIND => FinishedTest[h]];
Rosemary.PrintPeriod["Rosemary ran in", time, BasicTime.Now[]];
FinishedTest[h];
};
AlreadyStarted: ENTRY PROC [h: Tester] RETURNS [started: BOOL] = {
started ← h.testStarted OR h.singleEval;
h.testStarted ← TRUE;
BROADCAST h.proceed;
};
FinishedTest: ENTRY PROC [h: Tester] = {
UpdateDisplay[h.display];
ViewerTools.SetContents[h.runState, "Idle"];
h.testStarted ← FALSE;
BROADCAST h.proceed;
};
Interrupt: Buttons.ButtonProc = {
h: Tester ← NARROW[clientData];
SetEvalUntil[h: h, interrupt: TRUE, abort: FALSE];
};
Proceed: Buttons.ButtonProc = {
h: Tester ← NARROW[clientData];
SetEvalUntil[h: h, interrupt: FALSE, abort: FALSE];
};
Abort: Buttons.ButtonProc = {
h: Tester ← NARROW[clientData];
SetEvalUntil[h: h, interrupt: FALSE, abort: TRUE];
};
SingleEval: Buttons.ButtonProc = {
h: Tester ← NARROW[clientData];
DoSingleEval[h];
SetEvalUntil[h: h, interrupt: TRUE, abort: FALSE];
};
DoSingleEval: ENTRY PROC [h: Tester] = {
h.singleEval ← TRUE;
IF h.testStarted THEN {
h.interrupt ← TRUE;
UNTIL h.waiting OR NOT h.testStarted DO WAIT h.proceed ENDLOOP;
IF h.testStarted THEN {
count: INT ← h.evalSinceStartCount;
until: INT ← h.evalUntil;
h.evalUntil ← count+1;
h.interrupt ← FALSE;
BROADCAST h.proceed;
UNTIL h.evalSinceStartCount > count OR h.abort OR NOT h.testStarted DO
WAIT h.proceed;
ENDLOOP;
h.interrupt ← TRUE;
h.evalUntil ← until;
};
}
ELSE TRUSTED {
h.evalUntil ← 1;
h.testStarted ← TRUE;
Process.Detach[FORK DoStartTest[h]];
UNTIL h.waiting OR h.abort DO WAIT h.proceed ENDLOOP;
};
h.singleEval ← FALSE;
};
NextState: Buttons.ButtonProc = {
h: Tester ← NARROW[clientData];
DoNextState[h];
};
DoNextState: ENTRY PROC [h: Tester] = {
IF h.waiting AND NOT h.singleEval THEN {
IF h.displayedStatePoint=h.currentStatePoint THEN IO.PutRope[h.display.tsout, "No next state"]
ELSE {
h.displayedStatePoint ← NextBuf[h, h.displayedStatePoint];
Rosemary.RestoreState[h.display.simulation, h.displayedStatePoint];
UpdateESSC[h, h.evalSinceStartCount+1];
UpdateDisplay[h.display];
};
};
};
PreviousState: Buttons.ButtonProc = {
h: Tester ← NARROW[clientData];
DoPreviousState[h];
};
DoPreviousState: ENTRY PROC [h: Tester] = {
IF h.waiting AND NOT h.singleEval THEN {
ndsp: NATIF h.displayedStatePoint=0 THEN h.historySize-1 ELSE h.displayedStatePoint-1;
SELECT TRUE FROM
ndsp>=h.validStates => IO.PutRope[h.display.tsout, "No previous state, not enough evals since history trigger"];
ndsp=h.currentStatePoint => IO.PutRope[h.display.tsout, "No previous state, buffer depth exceeded"];
ENDCASE => {
h.displayedStatePoint ← ndsp;
Rosemary.RestoreState[h.display.simulation, h.displayedStatePoint];
UpdateESSC[h, h.evalSinceStartCount-1];
UpdateDisplay[h.display];
};
};
};
SetProceedUntil: PUBLIC PROC [tester: Tester, count: INT] = {
ViewerTools.SetContents[tester.proceedUntil, Convert.RopeFromInt[from: count, showRadix: FALSE]];
};
SetEvalUntil: PROC [h: Tester, interrupt: BOOL, abort: BOOL] = {
GetCount: PROC [v: ViewerClasses.Viewer] RETURNS [count: INT] = {
IF v=NIL THEN count ← 0
ELSE {
vRope: ROPE ← ViewerTools.GetContents[v];
count ← LAST[INT];
IF vRope#NIL THEN count ← Convert.IntFromRope[vRope ! Convert.Error => CONTINUE];
};
};
SetUntil[h, GetCount[h.proceedUntil], GetCount[h.historyTrigger], interrupt, abort];
};
SetUntil: ENTRY PROC [h: Tester, count: INT, trigger: INT, interrupt: BOOL, abort: BOOL] = {
IF NOT h.singleEval THEN {
h.evalUntil ← count;
h.historyTriggerCount ← trigger;
IF trigger>h.evalSinceStartCount THEN h.validStates ← 0;
h.interrupt ← interrupt;
};
h.abort ← abort;
BROADCAST h.proceed;
};
EvalsFinished: ENTRY PROC [h: Tester] RETURNS [abort: BOOL] = {
resetRunState: BOOLFALSE;
WHILE (h.evalUntil<=h.evalSinceStartCount OR h.interrupt) AND NOT h.abort DO
UpdateDisplay[h.display];
ViewerTools.SetContents[h.runState, "Interrupted"];
resetRunState ← TRUE;
BROADCAST h.proceed;
h.waiting ← TRUE;
WAIT h.proceed;
h.waiting ← FALSE;
ENDLOOP;
IF resetRunState THEN ViewerTools.SetContents[h.runState, "Running"];
abort ← h.abort;
};
UpdateDisplayButton: Buttons.ButtonProc = {
handle: RoseDisplay ← NARROW[clientData];
UpdateDisplay[handle];
};
UpdateDisplay: PUBLIC PROC [handle: RoseDisplay] = {
FOR dwvs: LIST OF ChoiceButtons.PromptDataRef ← handle.displayWires, dwvs.rest UNTIL dwvs=NIL DO
dw: CoreFlat.FlatWire ← NARROW[dwvs.first.clientdata];
new: ROPE;
value: Ports.LevelSequence ← Rosemary.WireValue[handle.simulation, dw];
new ← Ports.LevelSequenceToRope[value];
IF Rope.Length[new] > 16 THEN new ← "Typescript";
IF NOT Rope.Equal[new, ViewerTools.GetContents[dwvs.first.textViewer]] THEN ViewerTools.SetContents[dwvs.first.textViewer, new];
ENDLOOP;
};
NextBuf: PROC [h: Tester, buf: NAT] RETURNS [next: NAT] = {
next ← (buf + 1) MOD h.historySize;
};
UpdateESSC: PROC [h: Tester, new: INT] = {
h.evalSinceStartCount ← new;
ViewerTools.SetContents[h.evalsSinceStart, Convert.RopeFromCard[from: h.evalSinceStartCount, showRadix: FALSE]];
};
TesterDestroyed: ViewerEvents.EventProc ~ {
Called when the tester is destroyed, before erasure by window manager
ref: REF ANY ← ViewerOps.FetchProp[viewer, rosemaryHandle];
display: RoseDisplay;
IF event#destroy OR before#TRUE OR ref=NIL OR NOT ISTYPE[ref, RoseDisplay] THEN RETURN; -- someone is trying to play games with us
display ← NARROW[ref]; -- safe now
TRUSTED { Process.Detach[FORK TesterCleanup[display, viewer]] }; -- fork for safety
};
TesterCleanup: PROC [display: RoseDisplay, initial: ViewerClasses.Viewer] ~ {
Remove extraneous viewers, destroy backing file, remove circular references
CoreProperties.PutCellTypeProp[display.cellType, rosemaryHandle, NIL]; -- circular ref
IF display.plot#NIL AND display.plot.private#NIL THEN PlotGraph.DeletePlot[display.plot];
ViewerOps.DestroyViewer[ViewerIO.GetViewerFromStream[display.tsout]];
IF display.ps#NIL THEN {IO.SetLength[display.ps, 0]; IO.Close[display.ps]}; -- backing file
};
RoseDisplayFor: PUBLIC PROC [cellType: Core.CellType] RETURNS [RoseDisplay] ~ {
RETURN [NARROW [CoreProperties.GetCellTypeProp[cellType, rosemaryHandle]]];
};
Management of typescript
DebugEvalProcSignal: SIGNAL [port: Ports.Port, state: REF ANY] = CODE;
DebugEvalProc: PROC [port: Ports.Port, state: REF ANY] = {
SIGNAL DebugEvalProcSignal[port, state];
};
ExploreDesign: ReadEvalPrint.ClientProc = {
ENABLE CoreFlat.PathError => {result ← msg; GOTO Done};
Chop: PROC RETURNS [first, rest: ROPENIL ] = {
dStream: IO.STREAM = IO.RIS[command];
first ← dStream.GetTokenRope[IO.IDProc ! IO.EndOfStream => CONTINUE].token;
rest ← command.Substr[dStream.GetIndex];
};
handle: RoseDisplay ← NARROW[h.clientData];
operation, rest, pathName, fullPathName, wireName: ROPE;
[operation, rest] ← Chop[];
pathName ← ViewerTools.GetContents[handle.path];
fullPathName ← Rope.Cat[pathName, rest];
wireName ← Rope.Cat[pathName, ViewerTools.GetContents[handle.currentWire], rest];
SELECT TRUE FROM
Rope.Equal[operation, "a"] => {
wireKey: CoreFlat.FlatWire ← NEW[CoreFlat.FlatWireRec];
wireKey^ ← CoreFlat.ParseWirePath[handle.cellType, wireName, handle.cutSet];
result ← AddWireToPlot[handle, wireKey];
};
Rope.Equal[operation, "c"] => {
resultStream: IO.STREAMIO.ROS[];
flatCell: CoreFlat.FlatCellTypeRec ← CoreFlat.ParseCellTypePath[handle.cellType, fullPathName, handle.cutSet];
cellType: Core.CellType ← CoreFlat.ResolveFlatCellType[handle.cellType, flatCell].cellType;
CoreOps.PrintCellType[cellType, resultStream];
result ← IO.RopeFromROS[resultStream];
};
Rope.Equal[operation, "cn"] => {
flatCell: CoreFlat.FlatCellTypeRec ← CoreFlat.ParseCellTypePath[handle.cellType, fullPathName, handle.cutSet];
cellType: Core.CellType ← CoreFlat.ResolveFlatCellType[handle.cellType, flatCell].cellType;
result ← CoreOps.InheritCellTypeName[cellType];
};
Rope.Equal[operation, "cp"] => {
resultStream: IO.STREAMIO.ROS[];
flatCell: CoreFlat.FlatCellTypeRec ← CoreFlat.ParseCellTypePath[handle.cellType, fullPathName, handle.cutSet];
cellType: Core.CellType ← CoreFlat.ResolveFlatCellType[handle.cellType, flatCell].cellType;
CoreOps.PrintWire[cellType.public, resultStream];
result ← Rope.Cat[CoreOps.InheritCellTypeName[cellType], ": ", IO.RopeFromROS[resultStream]];
};
Rope.Equal[operation, "d"] => result ← DescribeWire[goryDetails: TRUE, handle: handle, wireName: wireName];
Rope.Equal[operation, "p"] => {
PrintPort: PROC [public: Core.Wire] RETURNS [subWires: BOOLTRUE, quit: BOOLFALSE] = {
IF NOT Ports.WirePortType[cellType, public].levelType=composite THEN {
value: Ports.LevelSequence;
subWires ← FALSE;
thisWire.wire ← public;
result ← Rope.Cat[result, CoreOps.GetFullWireName[cellType.public, public], " - "];
value ← Rosemary.WireValue[handle.simulation, thisWire ! Rosemary.NotInstantiated => GOTO NotFound];
result ← Rope.Cat[result, Ports.LevelSequenceToRope[value], "\n"];
EXITS NotFound => result ← Rope.Cat[result, "No such wire\n"];
};
};
flatCell: CoreFlat.FlatCellTypeRec;
thisWire: CoreFlat.FlatWire ← NEW[CoreFlat.FlatWireRec];
cellType: Core.CellType;
flatCell ← CoreFlat.ParseCellTypePath[handle.cellType, fullPathName, handle.cutSet];
cellType ← CoreFlat.ResolveFlatCellType[handle.cellType, flatCell].cellType;
thisWire.flatCell ← flatCell;
thisWire.wireRoot ← public;
IF CoreOps.VisitWire[cellType.public, PrintPort] THEN ERROR;
};
Rope.Equal[operation, "r"] => {
wireKey: CoreFlat.FlatWire ← NEW[CoreFlat.FlatWireRec];
wireKey^ ← CoreFlat.ParseWirePath[handle.cellType, wireName, handle.cutSet];
IF NOT RemoveWireFromPlot[handle, wireKey] THEN result ← "No such wire plotted";
};
Rope.Equal[operation, "s"] => {
flatCell: CoreFlat.FlatCellType ← NEW[CoreFlat.FlatCellTypeRec];
roseInstance: Rosemary.RoseCellInstance;
flatCell^ ← CoreFlat.ParseCellTypePath[handle.cellType, fullPathName, handle.cutSet];
roseInstance ← NARROW[RefTab.Fetch[x: handle.simulation.coreToRoseInstances, key: flatCell].val];
IF roseInstance=NIL THEN result ← "No such instance"
ELSE TRUSTED {Process.Detach[FORK DebugEvalProc[roseInstance.publicPort, roseInstance.state]]};
};
Rope.Equal[operation, "t"] => result ← DescribeWire[goryDetails: FALSE, handle: handle, wireName: wireName];
Rope.Equal[operation, "v"] => result ← WireValueRope[handle, wireName];
ENDCASE => IF NOT Rope.IsEmpty[operation] THEN result ← Rope.Cat["No such command: ", operation, ", commands are (a)dd wire to plot, (r)emove wire from plot, (s)tate, (v)alue, (t)race, (p)orts, (c)elltype, (c)elltype(n)ame, (c)elltype(p)ublic, and (d)rivers"];
EXITS
Done => NULL;
};
WireValueRope: PROC [handle: RoseDisplay, wireName: ROPE] RETURNS [rope: ROPE] = {
key: CoreFlat.FlatWire ← NEW[CoreFlat.FlatWireRec];
key^ ← CoreFlat.ParseWirePath[handle.cellType, wireName, handle.cutSet];
rope ← Ports.LSToRope[Rosemary.WireValue[handle.simulation, key ! Rosemary.NotInstantiated => GOTO NotFound]];
EXITS NotFound => rope ← "No such wire";
};
DescribeWire: PROC [goryDetails: BOOL, handle: RoseDisplay, wireName: ROPE] RETURNS [result: ROPE] = {
wireKey: CoreFlat.FlatWire ← NEW[CoreFlat.FlatWireRec];
parentKey: CoreFlat.FlatWire ← NEW[CoreFlat.FlatWireRec];
roseWire: Rosemary.RoseWire;
wireKey^ ← CoreFlat.ParseWirePath[handle.cellType, wireName, handle.cutSet];
parentKey^ ← CoreFlat.CanonizeWire[handle.cellType, wireKey^];
roseWire ← NARROW[RefTab.Fetch[handle.simulation.coreToRoseWires, parentKey].val];
result ← IF roseWire=NIL THEN Rope.Cat[CoreFlat.WirePathRope[handle.cellType, wireKey^], " = ", WireValueRope[handle, wireName], "\nThis wire is not stored as a Rosemary wire, try a parent or child of it"]
ELSE RoseWirePrint[goryDetails, handle.simulation, handle.cellType, roseWire];
};
RoseWirePrint: PROC [goryDetails: BOOL, simulation: Rosemary.Simulation, root: Core.CellType, roseWire: Rosemary.RoseWire] RETURNS [result: ROPE] = {
CatWire: PROC [roseWire: Rosemary.RoseWire] = {
result ← Rope.Cat[result, CoreFlat.WirePathRope[root, roseWire.wire], " - "];
IF roseWire.currentValue=NIL THEN {
IF goryDetails THEN {
result ← Rope.Cat[result, "m: ", IF roseWire.memory THEN "T" ELSE "F"];
result ← Rope.Cat[result, "cd: ", Ports.driveNames[roseWire.connectionDrive]];
result ← Rope.Cat[result, ", sd: ", Ports.driveNames[roseWire.switchDrive]];
result ← Rope.Cat[result, ", ud: ", Ports.driveNames[roseWire.upDrive]];
result ← Rope.Cat[result, ", dd: ", Ports.driveNames[roseWire.downDrive]];
result ← Rope.Cat[result, ", wd: ", Ports.driveNames[roseWire.wireDrive]];
result ← Rope.Cat[result, ", cl: ", Ports.levelNames[roseWire.connectionLevel], ", "];
};
result ← Rope.Cat[result, "wl: ", Ports.levelNames[roseWire.wireLevel]];
}
ELSE result ← Ports.LevelSequenceToRope[roseWire.currentValue];
};
CatTran: PROC [roseTran: Rosemary.RoseTransistor] = {
result ← Rope.Cat[result, " tt: ", CoreClasses.transistorTypeNames[roseTran.type]];
IF goryDetails THEN result ← Rope.Cat[result, ", tc: ", Ports.driveNames[roseTran.conductivity]];
};
FindPortWire: Ports.EachWirePortPairProc = {
IF (quit ← port=clientPort) THEN coreWire.wire ← wire;
};
clientPort: Ports.Port;
coreWire: CoreFlat.FlatWireRec;
coreWire.wireRoot ← public;
CatWire[roseWire];
result ← Rope.Cat[result, "\nConnections\n"];
IF roseWire.connections#NIL THEN FOR fields: NAT IN [0..roseWire.connections.size) DO
field: Rosemary.Field ← roseWire.connections[fields];
roseInstance: Rosemary.RoseCellInstance ← field.portBinding.instance;
result ← Rope.Cat[result, " "];
clientPort ← field.portBinding.clientPort;
IF NOT Ports.VisitBinding[wire: IF roseInstance=NIL THEN root.public ELSE CoreFlat.ResolveFlatCellType[root, roseInstance.instance].cellType.public, port: IF roseInstance=NIL THEN simulation.testPort ELSE roseInstance.publicPort, eachWirePortPair: FindPortWire] THEN ERROR;
coreWire.flatCell ← IF roseInstance=NIL THEN CoreFlat.rootCellType ELSE roseInstance.instance;
result ← Rope.Cat[result, CoreFlat.WirePathRope[root, coreWire]];
IF goryDetails THEN {
result ← Rope.Cat[result, " - d: "];
IF clientPort.driveType=aggregate THEN result ← Rope.Cat[result, Ports.driveNames[field.portBinding.currentDrive]]
ELSE FOR i: NAT IN [0..field.currentDrive.size) DO
IF i=0 THEN result ← Rope.Cat[result, "("];
result ← Rope.Cat[result, Ports.driveNames[field.currentDrive[i]]];
result ← Rope.Cat[result, IF i=field.currentDrive.size-1 THEN ")" ELSE ", "];
ENDLOOP;
result ← Rope.Cat[result, ", v: ", Ports.LevelSequenceToRope[field.currentValue]];
result ← Rope.Cat[result, ", sb: ", Convert.RopeFromCard[field.portStartBit]];
result ← Rope.Cat[result, ", fs: ", Convert.RopeFromCard[field.currentValue.size]];
};
result ← Rope.Cat[result, "\n"];
ENDLOOP;
result ← Rope.Cat[result, "Channels\n"];
IF roseWire.channels#NIL THEN FOR channels: NAT IN [0..roseWire.channels.size) DO
tran: Rosemary.RoseTransistor ← roseWire.channels[channels];
otherWire: Rosemary.RoseWire ← tran.ch1;
CatTran[tran];
result ← Rope.Cat[result, "\n oc: "];
IF otherWire=roseWire THEN otherWire ← tran.ch2;
CatWire[otherWire];
result ← Rope.Cat[result, "\n g: ", ];
CatWire[tran.gate];
result ← Rope.Cat[result, "\n"];
ENDLOOP;
result ← Rope.Cat[result, "Gates\n"];
IF roseWire.gates#NIL THEN FOR gates: NAT IN [0..roseWire.gates.size) DO
tran: Rosemary.RoseTransistor ← roseWire.gates[gates];
CatTran[tran];
result ← Rope.Cat[result, "\n c1: "];
CatWire[tran.ch1];
result ← Rope.Cat[result, "\n c2: ", ];
CatWire[tran.ch2];
result ← Rope.Cat[result, "\n"];
ENDLOOP;
};
Wire Display
DisplayPortLeafWires: PUBLIC PROC [root: Core.CellType, flatCell: CoreFlat.FlatCellTypeRec ← CoreFlat.rootCellType] RETURNS [displayWires: CoreFlat.FlatWires] = {
CompareFlatWires: List.CompareProc = {
flat1: CoreFlat.FlatWire ← NARROW[ref1];
flat2: CoreFlat.FlatWire ← NARROW[ref2];
IF flat1.flatCell.path.length>flat2.flatCell.path.length THEN RETURN [greater];
IF flat1.flatCell.path.length<flat2.flatCell.path.length THEN RETURN [less];
FOR i: CoreFlat.InstancePathIndex IN [0..flat1.flatCell.path.length) DO
one: BOOL ← flat1.flatCell.path.bits[i];
other: BOOL ← flat2.flatCell.path.bits[i];
SELECT TRUE FROM
one AND (NOT other) => RETURN [greater];
(NOT one) AND other => RETURN [less];
ENDCASE;
ENDLOOP;
IF flat1.flatCell.recastCount>flat2.flatCell.recastCount THEN RETURN [greater];
IF flat1.flatCell.recastCount<flat2.flatCell.recastCount THEN RETURN [less];
RETURN[Rope.Compare[CoreOps.GetFullWireName[cell.public, flat1.wire], CoreOps.GetFullWireName[cell.public, flat2.wire]]];
};
FindLeaves: CoreOps.EachWireProc = {
IF Ports.WirePortType[cell, wire].levelType#composite THEN {
subWires ← FALSE;
displayWires ← CONS[NEW[CoreFlat.FlatWireRec ← [flatCell: flatCell, wireRoot: public, wire: wire]], displayWires];
};
};
cell: Core.CellType ← CoreFlat.ResolveFlatCellType[root, flatCell].cellType;
IF CoreOps.VisitWire[cell.public, FindLeaves] THEN ERROR;
TRUSTED {displayWires ← LOOPHOLE[List.Sort[LOOPHOLE[displayWires], CompareFlatWires]]};
};
Backup file management
Current misfeatures and how to correct them:
- using RedBlackTree is not very efficient, but it is not critical to efficiency since about anything is better than reading bits from the disk... Rewriting equivalent specialized code is both tedious & non-kosher.
- reading in strictly decreasing order is not very good due to the forwards buffering of FS. I don't see any way out of that, save by making the file management much more complex. Performance seems adequate now, especially if PlotGraph is corrected to pass a correct window...
- some performance might be gained by using IO.PutBlock/IO.GetBlock, but this is real boring...
- the backing file (handle.ps) never gets closed properly, nor erased/destroyed, resulting in an excessive wastage of disk space. This should be done by the same code (now in LogicRosemaryImpl, but should move out) that destroys cleanly the tester viewer and similar crap. An alternative is to finalize RoseDisplay, but that sounds as an extreme measure for such a simple thing...
- It would be real fine to save the drive with which a wire has been driven, in order to distinguish X from Z (driven to garbage from tri-stated). Two problems for that: RoseWires that have LS attached do not carry the drive around (Rick says that can be twiddled around), and PlotGraph does not provide the capacity to display that information. The nice thing is that the cost in storage/disk is very small...
Sample: TYPE = REF SampleRec;
SampleRec: TYPE = RECORD [
next: Sample ← NIL,
time: INTLAST[INT],
value: SEQUENCE size: CARDINAL OF Ports.Level
];
The value of the rose wire at time was value, until a further sample
SampleList: TYPE = REF SampleListRec;
SampleListRec: TYPE ~ RECORD [
head: Sample, -- the head of the list. The tail is always WireData.current
beforeHeadPos: INT, -- the file position of the predecessor of head, <0 => no predecessor
inTable: BOOLFALSE -- used internally to RBT management, don't modify ...
];
The in-memory representation of a list of samples. The samples back in time may still be on disk, starting at beforeHeadPos.
WireData: TYPE = REF WireDataRec;
WireDataRec: TYPE = RECORD [
lastFilePos: INT ← -1, -- the file position of the last sample written
list: SampleList ← NIL, -- if non-nil, the samples for this wire are currently kept in memory
previous: Sample ← NIL, -- the previous sample
current: Sample ← NIL -- the sample currently taken, not guaranteed if time=lastUpdateTime
];
The data field of a RoseWire. The following assertions always hold:
- current.next = NIL
- current.time = value of time at last call to Update
- current will never be modified once time of Update call > current.time
- previous is the sample that was valid just before current. It is necessary to allow collapsing curent with a previous value. previous has not yet been written to disk!
- if previous#NIL, then previous.next=current
- if list#NIL, then samples are kept in memory. Then, either list.head=previous=NIL, or previous may be reached by following next links from list.head (possibly 0 times!).
LockBackingStore: ENTRY PROC [h: RoseDisplay] = {
UNTIL NOT h.psLock DO WAIT h.psWait ENDLOOP;
h.psLock ← TRUE;
};
UnlockBackingStore: ENTRY PROC [h: RoseDisplay] = {
h.psLock ← FALSE;
BROADCAST h.psWait;
};
WriteSample: PROC [s: IO.STREAM, sample: Sample, prvPos: INT] RETURNS [pos: INT] ~ {
Write the specified sample at the current file position, using prvPos as backpointer, and return in pos the fileposition at which this sample was read. The initial and final file position are at EOF.
Must be called under backing store lock!
pos ← IO.GetIndex[s];
IO.PutFWord[s, LOOPHOLE[prvPos]]; -- write position
IO.PutFWord[s, LOOPHOLE[sample.time]]; -- write time
FOR i: NAT IN [0..sample.size) DO
IO.PutByte[s, LOOPHOLE[sample.value[i]]];
ENDLOOP;
};
ReadSample: PROC [s: IO.STREAM, at: INT, into: Sample] RETURNS [prv: INT] ~ {
Read a sample from the specified location on the file. prv is the pointer to the previous sample in the chain. The initial and final file position are undefined.
Must be called under backing store lock!
IO.SetIndex[s, at];
prv ← LOOPHOLE[IO.GetFWord[s]];
into.time ← LOOPHOLE[IO.GetFWord[s]];
FOR i: NAT IN [0..into.size) DO
into.value[i] ← LOOPHOLE[IO.GetByte[s]];
ENDLOOP;
};
SampleWire: PROC [wire: Rosemary.RoseWire, sample: Sample] ~ {
Copy the value of the wire into the sample's data field
IF wire.currentValue=NIL THEN sample.value[0] ← wire.wireLevel
ELSE FOR i: NAT IN [0.. sample.size) DO sample.value[i] ← wire.currentValue[i] ENDLOOP;
};
DifferSample: PROC [s1, s2: Sample] RETURNS [BOOL] ~ {
Returns TRUE iff the two samples have different value fields (size must be same!)
FOR i: CARDINAL IN [0..s1.size)
DO IF s1.value[i]#s2.value[i] THEN RETURN [TRUE]
ENDLOOP;
RETURN [FALSE];
};
InitializeDeltas: PUBLIC PROC [handle: RoseDisplay] = {
NewWireData: RefTab.EachPairAction = {
Create the Wire data and initialize it properly...
wire: Rosemary.RoseWire ← NARROW[val];
data: WireData ← NEW [WireDataRec];
nbits: CARDINAL = IF wire.currentValue=NIL THEN 1 ELSE wire.currentValue.size;
data.lastFilePos ← -1;
data.list ← NIL; -- not kept in memory (yet...)
data.previous ← NIL; -- no previous sample
data.current ← NEW[SampleRec[nbits]];
data.current.time ← 0; data.current.next ← NIL; SampleWire[wire, data.current];
wire.data ← data;
};
LockBackingStore[handle]; -- Don't forget it here...
IF handle.ps=NIL THEN handle.ps ← FS.StreamOpen["///Temp/RoseBackingStore.bin", $create] ELSE IO.SetLength[handle.ps, 0]; -- completely erase the file if already there...
[] ← RefTab.Pairs[x: handle.simulation.coreToRoseWires, action: NewWireData];
handle.lastValidTime ← 0;
UnlockBackingStore[handle]; -- we don't want to hold onto it during plotting !
IF handle.plot#NIL AND handle.plot.data#NIL THEN PlotGraph.RefreshPlot[plot: handle.plot, eraseFirst: TRUE];
};
RecordDelta: PUBLIC PROC [handle: RoseDisplay, wire: Rosemary.RoseWire, time: INT] ~ {
Maintain the wire data field when the wire is updated.
data: WireData = NARROW [wire.data];
nbits: NAT = data.current.size;
LockBackingStore[handle]; -- let's be paranoid...
At this point, we assume the file pointer to be at EOF
IF time#data.current.time THEN { -- this is a different Eval
SELECT TRUE FROM
data.previous=NIL => { -- this the 1st sample, make it previous & allocate new current
data.previous ← data.current;
data.current ← NEW[SampleRec[nbits]];
data.previous.next ← data.current;
data.current.next ← NIL; -- just to make sure...
};
DifferSample[data.previous, data.current] => { -- commit current sample
data.lastFilePos ← WriteSample[handle.ps, data.previous, data.lastFilePos]; -- write it
data.current.next ← IF data.list=NIL THEN data.previous ELSE NEW[SampleRec[nbits]];
data.previous ← data.current; data.current ← data.previous.next; -- update data
data.current.next ← NIL; -- necessary if data.previous was reused
};
ENDCASE => NULL; -- collapse current with previous
data.current.time ← time; -- this is the new date for the curent sample
};
SampleWire[wire, data.current]; -- keep current up to date
UnlockBackingStore[handle];
};
RBTGetKey: RedBlackTree.GetKey ~ {
Get the key for a SampleList in the RedBlackTree: it's identity...
RETURN [data]; -- keys are same as data...
};
RBTCompare: RedBlackTree.Compare ~ {
Compare key & data in the RedBlackTree
list1: SampleList = NARROW [k];
list2: SampleList = NARROW [data];
RETURN [Basics.CompareInt[list1.beforeHeadPos, list2.beforeHeadPos]];
};
EnterRoseValuesInRBT: PROC [table: RedBlackTree.Table, roseValues: Rosemary.RoseValues, min: INT ← 0] RETURNS [newTable: RedBlackTree.Table] ~ {
Add the specified roseValues to the table so that they will be fetched from disk up to the required minimum time. The initial table may be empty.
The RedBlackTree is used to read as much information as possible in a single pass over the sample file.
This is the only place where SampleList are created and beforeHeadPos modified.
Must be called under backing store lock!
UNTIL roseValues=NIL DO
data: WireData = NARROW [roseValues.first.roseWire.data];
list: SampleList;
roseValues ← roseValues.rest; -- advance list pointer
IF data=NIL THEN LOOP; -- InitializeDeltas has not been called yet, discard...
list ← data.list;
IF list=NIL THEN { -- must create list head
list ← NEW [SampleListRec ← [
head: IF data.previous#NIL THEN data.previous ELSE data.current,
beforeHeadPos: data.lastFilePos,
inTable: FALSE]];
data.list ← list;
};
IF list.beforeHeadPos<0 OR list.head.time<=min THEN LOOP; -- nothing to fetch
IF table=NIL THEN table ← RedBlackTree.Create[RBTGetKey, RBTCompare];
IF NOT list.inTable THEN { -- may not insert twice in a RedBlackTree
RedBlackTree.Insert[table, list, list];
list.inTable ← TRUE;
};
ENDLOOP;
newTable ← table;
};
ReadSamplesUsingRBT: PROC [stream: IO.STREAM, table: RedBlackTree.Table, min: INT] ~ {
Read all the samples described in the RBT, going backwards & in order through the file.
Must be called under backing store lock!
initialPosition: INT = IO.GetIndex[stream]; -- save current file position (EOF)
IF table#NIL THEN WHILE RedBlackTree.Size[table]#0 DO
list: SampleList ← NARROW [RedBlackTree.LookupLargest[table]];
node: RedBlackTree.Node ← RedBlackTree.Delete[table, list];
sample: Sample ← NEW [SampleRec[list.head.size]];
list.beforeHeadPos ← ReadSample[stream, list.beforeHeadPos, sample];
sample.next ← list.head; list.head ← sample; -- insert as head of list
IF sample.time>min AND list.beforeHeadPos>=0 THEN RedBlackTree.InsertNode[table, node, list] -- this one must still go on...
ELSE list.inTable ← FALSE; -- it's no more there...
ENDLOOP;
IO.SetIndex[stream, initialPosition]; -- go back to where we came from
};
Plotting
Known misfeatures and how to improve on them:
- PlotGraph almost always calls the GraphEnumerateProc with bounds=infinite (on fact, due to a PlotGraph bug, empty). As a result, when a new wire is displayed, it is always read back to the origin of times. It is not possible from the client code to infer the correct bounds (gathering them from the viewer will hamper IP generation). This should be fixed in PlotGraph (anyway, there are other things to be fixed there...).
- It would be more efficient to add multiple wires to the plot at once than one at a time because refreshing requires reading the disk. Either have AddWiresToPlot instead of AddWireToPlot, or (better) leave the responsability of refreshing to the client.
- It would be faster is InitPlotGraphData could avoid rewinding back to the beginning of times. This could be achieved by adding a "currentTime" field to the pgd, but this raises serious questions when the simulation is re-initialized because we then must invalidate all pgd's.
PlotGraphData: TYPE ~ REF PlotGraphDataRec;
PlotGraphDataRec: TYPE ~ RECORD [
roseValues: Rosemary.RoseValues ← NIL, -- the roseValues that constitute the flat wire
bits: Ports.LevelSequence ← NIL, -- computed values will go in there
parts: SEQUENCE size: CARDINAL OF Sample -- basic information for reconstitution
];
This is the data structure that permits to recompute values of flatwires at each time
roseGraphClass: PlotGraph.GraphClass ← NEW[PlotGraph.GraphClassRec ← [
enumerate: RoseGraphEnumerate]];
NewPlotGraphData: PROC [rvl: Rosemary.RoseValues] RETURNS [pgd: PlotGraphData] ~ {
Create the PlotGraphData with the right sizes for the RoseValues.
partCount: NAT ← 0;
bitCount: NAT ← 0;
FOR vals: Rosemary.RoseValues ← rvl, vals.rest UNTIL vals=NIL DO
val: Rosemary.RoseValue = vals.first;
bitCount ← bitCount+val.fieldWidth;
partCount ← partCount + 1;
ENDLOOP;
pgd ← NEW[PlotGraphDataRec[partCount]];
pgd.roseValues ← rvl;
pgd.bits ← NEW[Ports.LevelSequenceRec[bitCount]];
};
InitPlotGraphData: PROC [handle: RoseDisplay, pgd: PlotGraphData, min: INT] RETURNS [ok: BOOLTRUE] ~ {
Initialize pgd parts so that a first sample is valid at the required time.
Assumes that the corresponding values have been made resident early enough using EnterRoseValuesInRBT/ReadSamplesUsingRBT.
Will return FALSE if no valid sample may be established for the required date
Must be called under backing store lock!
rvl: Rosemary.RoseValues ← pgd.roseValues;
FOR part: NAT IN [0..pgd.size) DO -- go up the sample chain up to min
data: WireData = NARROW[rvl.first.roseWire.data];
sample: Sample;
IF data=NIL OR data.list=NIL THEN RETURN [ok: FALSE]; -- not initialized !
sample ← data.list.head;
IF sample.time>min THEN RETURN [ok: FALSE]; -- unable to go early enough ...
UNTIL sample.next=NIL OR sample.next.time>min DO sample ← sample.next ENDLOOP;
pgd.parts[part] ← sample;
rvl ← rvl.rest;
ENDLOOP;
};
AssemblePlotGraphDataBits: PROC [pgd: PlotGraphData] RETURNS [nextTime: INT] ~ {
Assemble the bits for the current time, and position everything for the next time. Returns LAST[INT] if the computed value is the last one.
Must be called under backing store lock!
firstFreeBit: NAT ← 0;
bits: Ports.LevelSequence ← pgd.bits;
nextTime ← LAST[INT];
FOR i: NAT IN [0..pgd.size) DO -- build up the bits
sample: Sample = pgd.parts[i];
FOR bit: NAT IN [0..sample.size) DO bits[firstFreeBit+bit] ← sample.value[bit] ENDLOOP;
firstFreeBit ← firstFreeBit+sample.size;
IF sample.next#NIL THEN nextTime ← MIN[sample.next.time, nextTime];
ENDLOOP;
IF nextTime=LAST[INT] THEN RETURN; -- nothing more to do...
FOR i: NAT IN [0..pgd.size) DO -- advance samples for nextTime
sample: Sample = pgd.parts[i];
IF sample.next#NIL AND sample.next.time=nextTime THEN pgd.parts[i] ← sample.next;
ENDLOOP;
};
AddWireToPlot: PUBLIC PROC [handle: RoseDisplay, wire: CoreFlat.FlatWire] RETURNS [msg: Rope.ROPENIL] = {
For reasons of efficiency, this procedure should NOT refresh the plot because refreshing the plot forces reading the backing store, and the more there is to do at once the better...
roseValues: Rosemary.RoseValues ← Rosemary.GetValues[handle.simulation, wire ! Rosemary.NotInstantiated => GOTO NoSuchWire];
plot: PlotGraph.Plot ← handle.plot;
pgd: PlotGraphData ← NewPlotGraphData[roseValues];
graph: PlotGraph.Graph ← NEW[PlotGraph.GraphRec ← [
class: roseGraphClass,
data: pgd]];
axis: PlotGraph.Axis ← NEW[PlotGraph.AxisRec ← [
bounds: [0.0, 0.0, 10.0, 6.0],
name: CoreFlat.WirePathRope[handle.cellType, wire^],
axisData: [X: [ticks: 1.0, visible: TRUE], Y: [ticks: 1.0, visible: TRUE]],
graphs: LIST [graph]]];
IF Rope.Equal[Rope.Substr[axis.name, 0, 7], "public."] THEN axis.name ← Rope.Substr[axis.name, 7];
IF pgd.bits.size=1 THEN axis.style ← analog
ELSE {axis.style ← hexaV; axis.maxChars ← (pgd.bits.size+3)/4};
IF plot=NIL OR plot.data=NIL THEN { -- don't forget the plot might have been destroyed
plot ← handle.plot ← PlotGraph.CreatePlot[handle.name];
PlotGraph.LockPlot[plot];
plot.lowerBounds ← [0.0, 0.0];
plot.upperBounds ← [Real.Float[handle.lastValidTime], 5.0];
plot.data ← handle;
}
ELSE PlotGraph.LockPlot[plot];
IF plot.axis#NIL THEN axis.bounds ← plot.axis.first.bounds;
plot.axis ← CONS[axis, plot.axis];
PlotGraph.UnlockPlot[plot];
PlotGraph.RefreshPlot[plot: plot, eraseFirst: TRUE];
EXITS
NoSuchWire => msg ← "Not stored as a Rosemary wire";
};
RemoveWireFromPlot: PUBLIC PROC [handle: RoseDisplay, wire: CoreFlat.FlatWire] RETURNS [found: BOOLFALSE] = {
roseValues: Rosemary.RoseValues ← Rosemary.GetValues[handle.simulation, wire ! Rosemary.NotInstantiated => GOTO NoSuchWire];
plot: PlotGraph.Plot ← handle.plot;
trail: PlotGraph.AxisList ← NIL;
IF plot=NIL OR plot.data=NIL THEN RETURN;
PlotGraph.LockPlot[plot];
FOR axis: PlotGraph.AxisList ← plot.axis, axis.rest UNTIL axis=NIL DO
pgd: PlotGraphData ← NARROW[axis.first.graphs.first.data];
IF pgd.roseValues=roseValues THEN { -- roseValues are unique for a given FlatWire...
IF trail=NIL THEN plot.axis ← axis.rest ELSE trail.rest ← axis.rest; -- excise guilty axis
found ← TRUE;
EXIT;
};
trail ← axis;
ENDLOOP;
PlotGraph.UnlockPlot[plot];
PlotGraph.RefreshPlot[plot: plot, eraseFirst: TRUE];
EXITS
NoSuchWire => NULL;
};
DeltaFinished: PUBLIC PROC [handle: RoseDisplay, time: INT] = {
plot: PlotGraph.Plot ← handle.plot;
handle.lastValidTime ← time;
IF plot#NIL AND plot.data#NIL THEN { -- the plot viewer might have been destroyed !
oldUpper: REAL = plot.upperBounds.x;
newUpper: REAL = Real.Float[time];
plot.upperBounds.x ← newUpper;
PlotGraph.RefreshPlot[plot: plot, within: [oldUpper, 0.0, newUpper-oldUpper, 6.0]];
};
};
RoseGraphEnumerate: PlotGraph.GraphEnumerateProc = {
[plot: Plot, graph: Graph, bounds: Rectangle, eachPoint: PointProc, data: REF ANY]
handle: RoseDisplay ← NARROW [plot.data];
pgd: PlotGraphData ← NARROW [graph.data];
analog: BOOL = pgd.bits.size=1;
time: INTMAX [Real.InlineFix[bounds.x], 0];
maxTime: INT = handle.lastValidTime; -- should take bounds in account, but for now...
LockBackingStore[handle]; -- critical here...
IF NOT InitPlotGraphData[handle, pgd, time] THEN {
We don't have enough information yet. Try to make all the wires in the plot resident early enough simultaneously, in order to reduce disk activity.
table: RedBlackTree.Table ← NIL;
FOR al: PlotGraph.AxisList ← plot.axis, al.rest UNTIL al=NIL DO -- pass over all axis
FOR gl: PlotGraph.GraphList ← al.first.graphs, gl.rest UNTIL gl=NIL DO -- and all graphs
thisPGD: PlotGraphData = NARROW [gl.first.data];
table ← EnterRoseValuesInRBT[table, thisPGD.roseValues, time]; -- collect roseValues
ENDLOOP;
ENDLOOP;
IF table#NIL THEN ReadSamplesUsingRBT[handle.ps, table, time];
IF NOT InitPlotGraphData[handle, pgd, time] THEN { -- well, we did try...
UnlockBackingStore[handle]; -- Release before leaving
RETURN;
};
};
IF pgd.bits.size=1 THEN WHILE time<=maxTime DO -- analog plot
nextTime: INT = AssemblePlotGraphDataBits[pgd];
value: REAL = SELECT pgd.bits[0] FROM L => 0.0, X => 2.5, H => 5.0, ENDCASE => ERROR;
IF eachPoint[x: Real.Float[time], y: value] THEN EXIT;
IF eachPoint[x: Real.Float[MIN[nextTime, maxTime]], y: value] THEN EXIT;
time ← nextTime;
ENDLOOP
ELSE WHILE time<=maxTime DO -- hexa plot
nextTime: INT = AssemblePlotGraphDataBits[pgd];
IF eachPoint[x: Real.Float[time], y: 0.0, rope: Ports.LSToRope[pgd.bits]] THEN EXIT;
time ← nextTime;
ENDLOOP;
UnlockBackingStore[handle]; -- may now safely release
};
WireTimeValue: PUBLIC PROC [handle: RoseDisplay, flatWire: CoreFlat.FlatWire, time: INT] RETURNS [value: Ports.LevelSequence] = {
Return the value of a wire at a given time. Will return NIL if no value may be established for the required time.
roseValues: Rosemary.RoseValues ← Rosemary.GetValues[handle.simulation, flatWire];
pgd: PlotGraphData ← NewPlotGraphData[roseValues];
LockBackingStore[handle]; -- critical here...
IF NOT InitPlotGraphData[handle, pgd, time] THEN {
We don't have enough information yet. Read it from the sample file.
table: RedBlackTree.Table ← EnterRoseValuesInRBT[NIL, pgd.roseValues, time];
IF table#NIL THEN ReadSamplesUsingRBT[handle.ps, table, time];
IF NOT InitPlotGraphData[handle, pgd, time] THEN { -- well, we did try...
UnlockBackingStore[handle]; -- Release before leaving
RETURN [NIL];
};
};
[] ← AssemblePlotGraphDataBits[pgd];
value ← pgd.bits;
UnlockBackingStore[handle]; -- may now safely release
};
Odds and Ends
CheckCoverage: PUBLIC PROC [handle: RoseDisplay] RETURNS [ok: BOOL] ~ {
Returns TRUE iff all nodes in the simulation reach the H and L levels. Nodes that do not are printed on the simulation log. All information is recovered from the backing file.
The algorithm is:
- build an RBT containing all of the simulations roseWires
- perform an algorithm similar to ReadSamplesUsingRBT:
- when a wire has been both H and L, remove it from RBT
- when a wire reaches the starting point and it has not been through both values, log a message & remove it from RBT
Entry: TYPE ~ REF EntryRep;
levelSeen: TYPE ~ RECORD [wasH, wasL: BOOL];
EntryRep: TYPE ~ RECORD [
pos: INT ← -1,
roseWire: Rosemary.RoseWire, -- to print the wire name in case of error
sample: Sample, -- to read the sample
seen: SEQUENCE size: CARDINAL OF levelSeen
];
Works only for atomic RoseWires, this needs fixing ...
GetKey: RedBlackTree.GetKey ~ {
Get the key for a SampleList in the RedBlackTree: it's identity...
RETURN [data]; -- keys are same as data...
};
Compare: RedBlackTree.Compare ~ {
Compare key & data in the RedBlackTree
e1: Entry = NARROW [k];
e2: Entry = NARROW [data];
RETURN [Basics.CompareInt[e1.pos, e2.pos]];
};
MergeSample: PROC [entry: Entry, sample: Sample] RETURNS [allSeen: BOOLTRUE] ~ {
Add information from sample into entry, return TRUE iff the result has allSeen.
FOR i: CARDINAL IN [0..entry.size) DO
SELECT sample.value[i] FROM
H => {
entry.seen[i].wasH ← TRUE;
allSeen ← allSeen AND entry.seen[i].wasL;
};
L => {
entry.seen[i].wasL ← TRUE;
allSeen ← allSeen AND entry.seen[i].wasH;
};
ENDCASE => allSeen ← allSeen AND entry.seen[i].wasH AND entry.seen[i].wasL;
ENDLOOP;
};
InsertInRBT: RefTab.EachPairAction ~ {
wire: Rosemary.RoseWire = NARROW[val];
data: WireData = NARROW [wire.data];
nBits: CARDINAL = data.current.size;
entry: Entry ← NEW [EntryRep[nBits]];
entry.pos ← data.lastFilePos;
entry.roseWire ← wire;
entry.sample ← NEW [SampleRec[nBits]];
FOR i: CARDINAL IN [0..nBits) DO entry.seen[i] ← [FALSE, FALSE] ENDLOOP;
IF MergeSample[entry, data.current] THEN RETURN; -- all bits have seen H and L
IF data.previous#NIL AND MergeSample[entry, data.previous] THEN RETURN; -- H,L seen
SELECT TRUE FROM
entry.pos<0 => ReportEntry[entry]; -- we've seen all about it, and it fails
RedBlackTree.LookupNode[table, entry]=NIL => RedBlackTree.Insert[table, entry, entry];
ENDCASE => NULL; -- discard the entry, it's already in the RBT somehow ...
};
ReportEntry: PROC [entry: Entry] ~ {
Report on the Rosemary typescript information about this wire not seing H and L
The algorithm to find the correct flat wire for structured wires follows exactly the method used in RosemaryImpl.InitWire.
ReportAtomic: PROC [wire: Core.Wire] ~ {
msg: Rope.ROPE;
h, l: BOOL;
[wasH: h, wasL: l] ← entry.seen[bit];
bit ← bit+1;
SELECT TRUE FROM
h AND l => RETURN; -- good bit
h => msg ← "L";
l => msg ← "H";
ENDCASE => msg ← "H nor L";
flatWire.wire ← wire;
handle.tsout.PutF["Wire %g does not go through %g\n", IO.rope[CoreFlat.WirePathRope[handle.cellType, flatWire]], IO.rope[msg]];
ok ← FALSE; -- at least one bit in error
};
flatWire: CoreFlat.FlatWireRec ← entry.roseWire.wire;
wireSize: NAT = CoreOps.WireBits[flatWire.wire];
bit: NAT ← 0;
IF wireSize#entry.size THEN ERROR; -- we're in BIG trouble
IF wireSize>1 THEN CoreOps.VisitRootAtomics[flatWire.wire, ReportAtomic]
ELSE ReportAtomic[flatWire.wire];
};
table: RedBlackTree.Table ← RedBlackTree.Create[GetKey, Compare];
initialPosition: INT; -- to save & restore file position
LockBackingStore[handle]; -- the whole procedure assumes the backing store does not change
ok ← TRUE; -- until proven false ...
-- Build the Red-Black tree
[] ← RefTab.Pairs[x: handle.simulation.coreToRoseWires, action: InsertInRBT];
-- Read the backing file backwards
initialPosition ← IO.GetIndex[handle.ps]; -- save current file position (EOF)
WHILE RedBlackTree.Size[table]#0 DO
entry: Entry ← NARROW [RedBlackTree.LookupLargest[table]];
node: RedBlackTree.Node ← RedBlackTree.Delete[table, entry];
entry.pos ← ReadSample[handle.ps, entry.pos, entry.sample];
IF MergeSample[entry, entry.sample] THEN LOOP; -- entry has seen H and L, discard
IF entry.pos>=0 THEN RedBlackTree.InsertNode[table, node, entry] -- keep entry active
ELSE ReportEntry[entry]; -- no more samples, but disagreement
ENDLOOP;
IO.SetIndex[handle.ps, initialPosition]; -- go back to where we came from
UnlockBackingStore[handle];
};
END.