Skip to content

Commit 63e5e3c

Browse files
authored
feat(gsoc'24): Deferred Contentions (#4882) (#351)
1 parent c2e2288 commit 63e5e3c

File tree

4 files changed

+149
-42
lines changed

4 files changed

+149
-42
lines changed

src/simulator/src/contention.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* @class
3+
* ContentionPendingData
4+
*
5+
* Data structure to store pending contentions in the circuit.
6+
**/
7+
export default class ContentionPendingData {
8+
constructor() {
9+
// Map<Node, Set<Node>>
10+
this.contentionPendingMap = new Map();
11+
this.totalContentions = 0;
12+
}
13+
14+
// Adds
15+
add(ourNode, theirNode) {
16+
if (this.contentionPendingMap.has(ourNode)) {
17+
if (!this.contentionPendingMap.get(ourNode).has(theirNode)) this.totalContentions++;
18+
this.contentionPendingMap.get(ourNode).add(theirNode);
19+
return;
20+
}
21+
22+
this.totalContentions++;
23+
this.contentionPendingMap.set(ourNode, new Set([theirNode]));
24+
}
25+
26+
has(ourNode) {
27+
return this.contentionPendingMap.has(ourNode);
28+
}
29+
30+
// Removes contention entry ourNode -> theirNode.
31+
remove(ourNode, theirNode) {
32+
if (!this.contentionPendingMap.has(ourNode) || !this.contentionPendingMap.get(ourNode).has(theirNode)) return;
33+
34+
this.contentionPendingMap.get(ourNode).delete(theirNode);
35+
if (this.contentionPendingMap.get(ourNode).size == 0) this.contentionPendingMap.delete(ourNode);
36+
this.totalContentions--;
37+
}
38+
39+
// Removes all contentionPending entries for ourNode.
40+
// Since ourNode is strictly a NODE_OUTPUT, we should remove all contentions for the node when the
41+
// node resolves.
42+
removeAllContentionsForNode(ourNode) {
43+
if (!this.contentionPendingMap.has(ourNode)) return;
44+
45+
const contentionsForOurNode = this.contentionPendingMap.get(ourNode);
46+
for (const theirNode of contentionsForOurNode) this.remove(ourNode, theirNode);
47+
}
48+
49+
// Removes contention entry ourNode -> theirNode if the contention between them has resolved.
50+
removeIfResolved(ourNode, theirNode) {
51+
if (ourNode.bitWidth === theirNode.bitWidth && (ourNode.value === theirNode.value || ourNode.value === undefined))
52+
this.remove(ourNode, theirNode);
53+
}
54+
55+
removeIfResolvedAllContentionsForNode(ourNode) {
56+
if (!this.contentionPendingMap.has(ourNode)) return;
57+
58+
const contentionsForOurNode = this.contentionPendingMap.get(ourNode);
59+
for (const theirNode of contentionsForOurNode) this.removeIfResolved(ourNode, theirNode);
60+
}
61+
62+
size() {
63+
return this.totalContentions;
64+
}
65+
66+
// Returns a list of [ourNode, theirNode] for all contentions.
67+
nodes() {
68+
var items = [];
69+
for (const [ourNode, contentionSet] of this.contentionPendingMap) {
70+
for (const theirNode of contentionSet) items.push([ourNode, theirNode]);
71+
}
72+
73+
return items;
74+
}
75+
76+
}

src/simulator/src/engine.js

+17-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import miniMapArea from './minimap'
1313
import { resetup } from './setup'
1414
import { verilogModeGet } from './Verilog2CV'
1515
import { renderOrder, updateOrder } from './metadata'
16+
import ContentionPendingData from './contention';
1617

1718
/**
1819
* Core of the simulation and rendering algorithm.
@@ -405,16 +406,16 @@ export function play(scope = globalScope, resetNodes = false) {
405406

406407
simulationArea.simulationQueue.reset()
407408
plotArea.setExecutionTime() // Waveform thing
409+
resetNodeHighlights(scope);
408410
// Reset Nodes if required
409411
if (resetNodes || forceResetNodes) {
410412
scope.reset()
411413
simulationArea.simulationQueue.reset()
412414
forceResetNodesSet(false)
413415
}
414416

415-
// To store list of circuitselements that have shown contention but kept temporarily
416-
// Mainly to resolve tristate bus issues
417-
simulationArea.contentionPending = []
417+
// To store set of Nodes that have shown contention but kept temporarily
418+
simulationArea.contentionPending = new ContentionPendingData();
418419
// add inputs to the simulation queue
419420
scope.addInputs()
420421
// to check if we have infinite loop in circuit
@@ -426,25 +427,34 @@ export function play(scope = globalScope, resetNodes = false) {
426427
return
427428
}
428429
elem = simulationArea.simulationQueue.pop()
430+
429431
elem.resolve()
432+
430433
stepCount++
431434
if (stepCount > 1000000) {
432435
// Cyclic or infinite Circuit Detection
433436
showError(
434437
'Simulation Stack limit exceeded: maybe due to cyclic paths or contention'
435438
)
436-
errorDetectedSet(true)
437439
forceResetNodesSet(true)
438440
}
439441
}
440442
// Check for Contentions
441-
if (simulationArea.contentionPending.length) {
442-
showError('Contention at TriState')
443-
forceResetNodesSet(true)
443+
if (simulationArea.contentionPending.size() > 0) {
444+
for (const [ourNode, theirNode] of simulationArea.contentionPending.nodes()) {
445+
ourNode.highlighted = true;
446+
theirNode.highlighted = true;
447+
}
448+
449+
forceResetNodesSet(true);
444450
showError('Contention Error: One or more bus contentions in the circuit (check highlighted nodes)');
445451
}
446452
}
447453

454+
export function resetNodeHighlights(scope) {
455+
for (const node of scope.allNodes) node.highlighted = false;
456+
}
457+
448458
/**
449459
* Function to check for any UI update, it is throttled by time
450460
* @param {number=} count - this is used to force update

src/simulator/src/modules/TriState.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,14 @@ export default class TriState extends CircuitElement {
6767
this.output1.value = this.inp1.value // >>>0)<<(32-this.bitWidth))>>>(32-this.bitWidth);
6868
simulationArea.simulationQueue.add(this.output1)
6969
}
70-
simulationArea.contentionPending = simulationArea.contentionPending.filter(x => x !== this);
7170
} else if (
7271
this.output1.value !== undefined &&
73-
!simulationArea.contentionPending.includes(this)
72+
!simulationArea.contentionPending.has(this.output1)
7473
) {
7574
this.output1.value = undefined
7675
simulationArea.simulationQueue.add(this.output1)
7776
}
78-
simulationArea.contentionPending = simulationArea.contentionPending.filter(x => x !== this);
77+
simulationArea.contentionPending.removeAllContentionsForNode(this.output1);
7978
}
8079

8180
/**

src/simulator/src/node.js

+54-32
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from './engine'
1414
import Wire from './wire'
1515
import { colors } from './themer/themer'
16+
import ContentionMeta from './contention'
1617

1718
/**
1819
* Constructs all the connections of Node node
@@ -418,48 +419,69 @@ export default class Node {
418419
return
419420
}
420421

421-
if (this.type == 0) {
422+
// For input nodes, resolve its parents if they are resolvable at this point.
423+
if (this.type == NODE_INPUT) {
422424
if (this.parent.isResolvable()) {
423425
simulationArea.simulationQueue.add(this.parent)
424426
}
425427
}
428+
else if (this.type == NODE_OUTPUT) {
429+
// Since output node forces its value on its neighbours, remove its contentions.
430+
// An existing contention will now trickle to the other output node that was causing
431+
// the contention.
432+
simulationArea.contentionPending.removeAllContentionsForNode(this);
433+
}
426434

427435
for (var i = 0; i < this.connections.length; i++) {
428-
const node = this.connections[i]
429-
430-
if (node.value != this.value || node.bitWidth != this.bitWidth) {
431-
if (
432-
node.type == 1 &&
433-
node.value != undefined &&
434-
node.parent.objectType != 'TriState' &&
435-
!(node.subcircuitOverride && node.scope != this.scope) && // Subcircuit Input Node Output Override
436-
node.parent.objectType != 'SubCircuit'
437-
) {
438-
// Subcircuit Output Node Override
439-
this.highlighted = true
440-
node.highlighted = true
441-
var circuitName = node.scope.name
442-
var circuitElementName = node.parent.objectType
443-
showError(
444-
`Contention Error: ${this.value} and ${node.value} at ${circuitElementName} in ${circuitName}`
445-
)
446-
} else if (node.bitWidth == this.bitWidth || node.type == 2) {
447-
if ((node.parent.objectType == 'TriState' || node.parent.objectType == 'ControlledInverter') && node.value != undefined) {
448-
if (node.parent.state.value) {
449-
simulationArea.contentionPending.push(node.parent)
436+
const node = this.connections[i];
437+
438+
switch (node.type) {
439+
// TODO: For an output node, a downstream value (value given by elements other than the parent)
440+
// should be overwritten in contention check and should not cause contention.
441+
case NODE_OUTPUT:
442+
if (node.value != this.value || node.bitWidth != this.bitWidth) {
443+
// Check contentions
444+
if (node.value != undefined && node.parent.objectType != 'SubCircuit'
445+
&& !(node.subcircuitOverride && node.scope != this.scope)) {
446+
// Tristate has always been a pain in the ass.
447+
if ((node.parent.objectType == 'TriState' || node.parent.objectType == 'ControlledInverter') && node.value != undefined) {
448+
if (node.parent.state.value) {
449+
simulationArea.contentionPending.add(node, this);
450+
break;
451+
}
452+
}
453+
else {
454+
simulationArea.contentionPending.add(node, this);
455+
break;
450456
}
451457
}
452-
453-
node.bitWidth = this.bitWidth
454-
node.value = this.value
455-
simulationArea.simulationQueue.add(node)
456458
} else {
457-
this.highlighted = true
458-
node.highlighted = true
459-
showError(
460-
`BitWidth Error: ${this.bitWidth} and ${node.bitWidth}`
461-
)
459+
// Output node was given an agreeing value, so remove any contention
460+
// entry between these two nodes if it exists.
461+
simulationArea.contentionPending.remove(node, this);
462+
}
463+
464+
// Fallthrough. NODE_OUTPUT propagates like a contention checked NODE_INPUT
465+
case NODE_INPUT:
466+
// Check bitwidths
467+
if (this.bitWidth != node.bitWidth) {
468+
this.highlighted = true;
469+
node.highlighted = true;
470+
showError(`BitWidth Error: ${this.bitWidth} and ${node.bitWidth}`);
471+
break;
472+
}
473+
474+
// Fallthrough. NODE_INPUT propagates like a bitwidth checked NODE_INTERMEDIATE
475+
case NODE_INTERMEDIATE:
476+
477+
if (node.value != this.value || node.bitWidth != this.bitWidth) {
478+
// Propagate
479+
node.bitWidth = this.bitWidth;
480+
node.value = this.value;
481+
simulationArea.simulationQueue.add(node);
462482
}
483+
default:
484+
break;
463485
}
464486
}
465487
}

0 commit comments

Comments
 (0)