Skip to content

feat(gsoc'24): Deferred Contentions (#4882) #351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/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;
}

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

/**
* Core of the simulation and rendering algorithm.
Expand Down Expand Up @@ -405,16 +406,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 @@ -426,25 +427,34 @@ 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 Contentions
if (simulationArea.contentionPending.length) {
showError('Contention at TriState')
forceResetNodesSet(true)
if (simulationArea.contentionPending.size() > 0) {
for (const [ourNode, theirNode] of simulationArea.contentionPending.nodes()) {
ourNode.highlighted = true;
theirNode.highlighted = true;
}

forceResetNodesSet(true);
showError('Contention Error: One or more bus contentions in the circuit (check highlighted nodes)');
}
}

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 src/simulator/src/modules/TriState.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,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 = simulationArea.contentionPending.filter(x => x !== this);
} else if (
this.output1.value !== undefined &&
!simulationArea.contentionPending.includes(this)
!simulationArea.contentionPending.has(this.output1)
) {
this.output1.value = undefined
simulationArea.simulationQueue.add(this.output1)
}
simulationArea.contentionPending = simulationArea.contentionPending.filter(x => x !== this);
simulationArea.contentionPending.removeAllContentionsForNode(this.output1);
}

/**
Expand Down
86 changes: 54 additions & 32 deletions src/simulator/src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
} from './engine'
import Wire from './wire'
import { colors } from './themer/themer'
import ContentionMeta from './contention'

Check failure

Code scanning / ESLint

disallow unused variables Error

'ContentionMeta' is defined but never used.

/**
* Constructs all the connections of Node node
Expand Down Expand Up @@ -418,48 +419,69 @@
return
}

if (this.type == 0) {
// For input nodes, resolve its parents if they are resolvable at this point.
if (this.type == NODE_INPUT) {

Check failure

Code scanning / ESLint

disallow the use of undeclared variables unless mentioned in `/*global */` comments Error

'NODE_INPUT' is not defined.
if (this.parent.isResolvable()) {
simulationArea.simulationQueue.add(this.parent)
}
}
else if (this.type == NODE_OUTPUT) {

Check failure

Code scanning / ESLint

disallow the use of undeclared variables unless mentioned in `/*global */` comments Error

'NODE_OUTPUT' is not defined.
// 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
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.parent.objectType == 'ControlledInverter') && node.value != undefined) {
if (node.parent.state.value) {
simulationArea.contentionPending.push(node.parent)
const node = this.connections[i];

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:

Check failure

Code scanning / ESLint

disallow the use of undeclared variables unless mentioned in `/*global */` comments Error

'NODE_OUTPUT' is not defined.
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.parent.objectType == 'ControlledInverter') && node.value != undefined) {
if (node.parent.state.value) {
simulationArea.contentionPending.add(node, this);
break;
}
}
else {
simulationArea.contentionPending.add(node, this);
break;
}
}

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}`
)
// 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;
showError(`BitWidth Error: ${this.bitWidth} and ${node.bitWidth}`);
break;
}

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

Check failure

Code scanning / ESLint

disallow the use of undeclared variables unless mentioned in `/*global */` comments Error

'NODE_INTERMEDIATE' is not defined.

if (node.value != this.value || node.bitWidth != this.bitWidth) {
// Propagate
node.bitWidth = this.bitWidth;
node.value = this.value;
simulationArea.simulationQueue.add(node);
}
default:
break;
Comment on lines +483 to +484

Check failure

Code scanning / ESLint

disallow fallthrough of `case` statements Error

Expected a 'break' statement before 'default'.
}
}
}
Expand Down
Loading