Skip to content

Commit

Permalink
Deferred Contentions (#4882)
Browse files Browse the repository at this point in the history
* Deferred Contentions

Before this commit, contentions were checked at output nodes
and if a disagreeing value was found (only exception being
Tristates), the simulation would error out with a contention
error. This is not correct since some contentions might occur
temporarily and might resolved in the same play() cycle down the
resolution queue.

This commit maintains an [effectively] set of all contentions
that are found during resolution. Some contentions might get
resolved in the simulation and they are removed from the set.
At the end of the play cycle, if any contentions remain, then only
those are real contentions and are reported.

This commit also removes the special contentionPending check for
Tristate as this should be handled by the new contention system.

* Add back Tristate special check

Turns out we need it.
  • Loading branch information
gr455 committed Apr 11, 2024
1 parent 9f6897a commit 5501064
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 28 deletions.
76 changes: 76 additions & 0 deletions simulator/src/contention.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @class
* ContentionPendingData
*
* Data structure to store pending contentions in the circuit.
**/
export default class ContentionPendingData {
constructor() {
// Map<Node, Set<Node>>
this.contentionPendingMap = new Map();
this.totalContentions = 0;
}

// Adds
add(ourNode, theirNode) {
if (this.contentionPendingMap.has(ourNode)) {
if (!this.contentionPendingMap.get(ourNode).has(theirNode)) this.totalContentions++;
this.contentionPendingMap.get(ourNode).add(theirNode);
return;
}

this.totalContentions++;
this.contentionPendingMap.set(ourNode, new Set([theirNode]));
}

has(ourNode) {
return this.contentionPendingMap.has(ourNode);
}

// Removes contention entry ourNode -> theirNode.
remove(ourNode, theirNode) {
if (!this.contentionPendingMap.has(ourNode) || !this.contentionPendingMap.get(ourNode).has(theirNode)) return;

this.contentionPendingMap.get(ourNode).delete(theirNode);
if (this.contentionPendingMap.get(ourNode).size == 0) this.contentionPendingMap.delete(ourNode);
this.totalContentions--;
}

// Removes all contentionPending entries for ourNode.
// Since ourNode is strictly a NODE_OUTPUT, we should remove all contentions for the node when the
// node resolves.
removeAllContentionsForNode(ourNode) {
if (!this.contentionPendingMap.has(ourNode)) return;

const contentionsForOurNode = this.contentionPendingMap.get(ourNode);
for (const theirNode of contentionsForOurNode) this.remove(ourNode, theirNode);
}

// Removes contention entry ourNode -> theirNode if the contention between them has resolved.
removeIfResolved(ourNode, theirNode) {
if (ourNode.bitWidth === theirNode.bitWidth && (ourNode.value === theirNode.value || ourNode.value === undefined))
this.remove(ourNode, theirNode);
}

removeIfResolvedAllContentionsForNode(ourNode) {
if (!this.contentionPendingMap.has(ourNode)) return;

const contentionsForOurNode = this.contentionPendingMap.get(ourNode);
for (const theirNode of contentionsForOurNode) this.removeIfResolved(ourNode, theirNode);
}

size() {
return this.totalContentions;
}

// Returns a list of [ourNode, theirNode] for all contentions.
nodes() {
var items = [];
for (const [ourNode, contentionSet] of this.contentionPendingMap) {
for (const theirNode of contentionSet) items.push([ourNode, theirNode]);
}

return items;
}

}
25 changes: 17 additions & 8 deletions simulator/src/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { showError } from './utils';
import miniMapArea from './minimap';
import { resetup } from './setup';
import { verilogModeGet } from './Verilog2CV';
import ContentionPendingData from './contention';

/**
* Core of the simulation and rendering algorithm.
Expand Down Expand Up @@ -379,16 +380,16 @@ export function play(scope = globalScope, resetNodes = false) {

simulationArea.simulationQueue.reset();
plotArea.setExecutionTime(); // Waveform thing
resetNodeHighlights(scope);
// Reset Nodes if required
if (resetNodes || forceResetNodes) {
scope.reset();
simulationArea.simulationQueue.reset();
forceResetNodesSet(false);
}

// To store list of circuitselements that have shown contention but kept temporarily
// Mainly to resolve tristate bus issues
simulationArea.contentionPending = [];
// To store set of Nodes that have shown contention but kept temporarily
simulationArea.contentionPending = new ContentionPendingData();
// add inputs to the simulation queue
scope.addInputs();
// to check if we have infinite loop in circuit
Expand All @@ -400,22 +401,30 @@ export function play(scope = globalScope, resetNodes = false) {
return;
}
elem = simulationArea.simulationQueue.pop();

elem.resolve();

stepCount++;
if (stepCount > 1000000) { // Cyclic or infinite Circuit Detection
showError('Simulation Stack limit exceeded: maybe due to cyclic paths or contention');
errorDetectedSet(true);
forceResetNodesSet(true);
}
}
// Check for TriState Contentions
if (simulationArea.contentionPending.length) {
showError('Contention at TriState');
forceResetNodesSet(true);
errorDetectedSet(true);
if (simulationArea.contentionPending.size() > 0) {
for (const [ourNode, theirNode] of simulationArea.contentionPending.nodes()) {
ourNode.highlighted = true;
theirNode.highlighted = true;
}

showError('Contention Error: One or more bus contentions in the circuit');
}
}

export function resetNodeHighlights(scope) {
for (const node of scope.allNodes) node.highlighted = false;
}

/**
* Function to check for any UI update, it is throttled by time
* @param {number=} count - this is used to force update
Expand Down
5 changes: 2 additions & 3 deletions simulator/src/modules/TriState.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,14 @@ export default class TriState extends CircuitElement {
this.output1.value = this.inp1.value; // >>>0)<<(32-this.bitWidth))>>>(32-this.bitWidth);
simulationArea.simulationQueue.add(this.output1);
}
simulationArea.contentionPending.clean(this);
} else if (
this.output1.value !== undefined &&
!simulationArea.contentionPending.contains(this)
!simulationArea.contentionPending.has(this.output1)
) {
this.output1.value = undefined;
simulationArea.simulationQueue.add(this.output1);
}
simulationArea.contentionPending.clean(this);
simulationArea.contentionPending.removeAllContentionsForNode(this.output1);
}

/**
Expand Down
65 changes: 48 additions & 17 deletions simulator/src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import Wire from './wire';
// import { colors } from './themer/themer';
import { colors } from './themer/themer';
import ContentionMeta from './contention'

/**
* Constructs all the connections of Node node
Expand Down Expand Up @@ -374,36 +375,66 @@ export default class Node {
return;
}

if (this.type == 0) {
// For input nodes, resolve its parents if they are resolvable at this point.
if (this.type == NODE_INPUT) {
if (this.parent.isResolvable()) { simulationArea.simulationQueue.add(this.parent); }
} else if (this.type == NODE_OUTPUT) {
// Since output node forces its value on its neighbours, remove its contentions.
// An existing contention will now trickle to the other output node that was causing
// the contention.
simulationArea.contentionPending.removeAllContentionsForNode(this);
}

for (var i = 0; i < this.connections.length; i++) {
const node = this.connections[i];

if (node.value != this.value || node.bitWidth != this.bitWidth) {
if (node.type == 1 && node.value != undefined
&& node.parent.objectType != 'TriState'
&& !(node.subcircuitOverride && node.scope != this.scope) // Subcircuit Input Node Output Override
&& node.parent.objectType != 'SubCircuit') { // Subcircuit Output Node Override
switch (node.type) {
// TODO: For an output node, a downstream value (value given by elements other than the parent)
// should be overwritten in contention check and should not cause contention.
case NODE_OUTPUT:
if (node.value != this.value || node.bitWidth != this.bitWidth) {
// Check contentions
if (node.value != undefined && node.parent.objectType != 'SubCircuit'
&& !(node.subcircuitOverride && node.scope != this.scope)) {
// Tristate has always been a pain in the ass.
if (node.parent.objectType == 'TriState' && node.value != undefined) {
if (node.parent.state.value) {
simulationArea.contentionPending.add(node, this);
break;
}
}
else {
simulationArea.contentionPending.add(node, this);
break;
}
}
} else {
// Output node was given an agreeing value, so remove any contention
// entry between these two nodes if it exists.
simulationArea.contentionPending.remove(node, this);
}

// Fallthrough. NODE_OUTPUT propagates like a contention checked NODE_INPUT
case NODE_INPUT:
// Check bitwidths
if (this.bitWidth != node.bitWidth) {
this.highlighted = true;
node.highlighted = true;
var circuitName = node.scope.name;
var circuitElementName = node.parent.objectType;
showError(`Contention Error: ${this.value} and ${node.value} at ${circuitElementName} in ${circuitName}`);
} else if (node.bitWidth == this.bitWidth || node.type == 2) {
if (node.parent.objectType == 'TriState' && node.value != undefined && node.type == 1) {
if (node.parent.state.value) { simulationArea.contentionPending.push(node.parent); }
}
showError(`BitWidth Error: ${this.bitWidth} and ${node.bitWidth}`);
break;
}

// Fallthrough. NODE_INPUT propagates like a bitwidth checked NODE_INTERMEDIATE
case NODE_INTERMEDIATE:

if (node.value != this.value || node.bitWidth != this.bitWidth) {
// Propagate
node.bitWidth = this.bitWidth;
node.value = this.value;
simulationArea.simulationQueue.add(node);
} else {
this.highlighted = true;
node.highlighted = true;
showError(`BitWidth Error: ${this.bitWidth} and ${node.bitWidth}`);
}
default:
break;
}
}
}
Expand Down

0 comments on commit 5501064

Please sign in to comment.