The demo below models a tiny traffic-light controller as a transition system. States are the light color; one transition is broken (it can skip straight to an illegal combination). Choose a bound k and click Run BMC — the tool unrolls the system k steps, builds the propositional formula, and searches for a path that hits the forbidden state.
<!-- BMC demo: traffic-light controller with one faulty transition -->
<div class="bmc-wrap">
<div class="controls">
<label for="bound-slider">Bound k = <strong id="bound-val">3</strong></label>
<input type="range" id="bound-slider" min="1" max="8" value="3" />
<button id="run-btn" type="button">Run BMC</button>
<button id="reset-btn" type="button" class="ghost">Reset</button>
</div>
<div class="result-box" id="result-box">
<span class="result-text" id="result-text">Choose a bound and click Run BMC.</span>
</div>
<div class="trace-area" id="trace-area"></div>
<details class="formula-details">
<summary>Show propositional formula</summary>
<pre class="formula-pre" id="formula-pre"></pre>
</details>
</div>
/* Bounded Model Checking demo — traffic-light styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.bmc-wrap { display: flex; flex-direction: column; gap: .8rem; }
/* control row */
.controls { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
label { font-size: .9rem; }
input[type=range] { flex: 1; min-width: 120px; }
button { font: 600 14px system-ui; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
/* result banner */
.result-box { border-radius: 8px; padding: .5rem .9rem; font-weight: 600; font-size: .95rem;
background: #e8eef3; border: 1px solid #cdd9e3; min-height: 2.4rem;
display: flex; align-items: center; }
.result-box.ok { background: #d4edda; border-color: #68b37c; color: #0a5c23; }
.result-box.fail { background: #f8d7da; border-color: #e27b85; color: #8b1c26; }
/* counterexample trace cards */
.trace-area { display: flex; flex-wrap: wrap; gap: .5rem; }
.step-card { border: 1px solid #cdd9e3; border-radius: 8px; padding: .5rem .7rem;
min-width: 90px; text-align: center; background: #f4f7fa; }
.step-card.bad { background: #fde8ea; border-color: #e27b85; }
.step-card .step-label { font-size: .72rem; color: #666; margin-bottom: .2rem; }
.step-card .light-icon { font-size: 1.6rem; line-height: 1; }
.step-card .state-name { font-size: .78rem; font-weight: 600; margin-top: .15rem; }
.step-card .transition { font-size: .7rem; color: #555; margin-top: .18rem; }
/* formula display */
.formula-details { font-size: .82rem; }
.formula-details summary { cursor: pointer; color: #1d3557; font-weight: 600; margin-bottom: .3rem; }
.formula-pre { background: #f0f4f8; border: 1px solid #cdd9e3; border-radius: 6px;
padding: .6rem .8rem; overflow-x: auto; white-space: pre-wrap; color: #333;
font-size: .78rem; line-height: 1.5; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Bounded Model Checking demo — traffic-light transition system
// System: 5 states, one faulty transition green->bug (red+green simultaneously).
// All states in the system
const STATES = ['red', 'red_yellow', 'green', 'yellow', 'bug'];
// Human-readable state labels
const STATE_LABEL = {
red: 'Red',
red_yellow: 'Red+Yellow',
green: 'Green',
yellow: 'Yellow',
bug: 'Red+Green (BUG)'
};
const STATE_ICON = {
red: '🔴', red_yellow: '🟡🔴', green: '🟢', yellow: '🟡', bug: '🔴🟢'
};
// Transition relation: maps each state to its successor states
// The faulty transition green->bug represents an illegal hardware state.
const TRANSITIONS = {
red: ['red_yellow'],
red_yellow: ['green'],
green: ['yellow', 'bug'],
yellow: ['red'],
bug: []
};
const INIT_STATE = 'red';
const BAD_STATE = 'bug';
// runBMC(k): BFS up to depth k looking for a path to BAD_STATE
// Returns the path if found, null if no counterexample within k steps.
function runBMC(k) {
// BFS queue: each entry is the path taken so far
const queue = [['red']];
while (queue.length > 0) {
const path = queue.shift();
const last = path[path.length - 1];
if (last === BAD_STATE) return path;
if (path.length - 1 >= k) continue;
for (const next of (TRANSITIONS[last] || [])) {
queue.push([...path, next]);
}
}
return null;
}
// buildFormulaText: display the propositional encoding for the current k
function buildFormulaText(k) {
const lines = [];
lines.push('Initial condition:');
lines.push(' s_0 = red');
lines.push('');
lines.push('Transition constraints (for each i in 0..k-1):');
for (const [src, dsts] of Object.entries(TRANSITIONS)) {
if (dsts.length > 0) {
lines.push(' s_i = ' + src + ' → s_{i+1} ∈ {' + dsts.join(', ') + '}');
}
}
lines.push('');
lines.push('BMC query (find BAD within k steps):');
lines.push(' s_0 = red ∧ T(s_0,s_1) ∧ ... ∧ T(s_{k-1},s_k) ∧ (s_0=bug ∨ ... ∨ s_k=bug)');
lines.push('');
lines.push(fmt('Bound: k = %k%', { k: k }));
lines.push(fmt('Variables: %k%%v% × |STATES| = %v2% boolean variables', { k: k, v: 1, v2: (k + 1) * STATES.length }));
return lines.join('\n');
}
// renderTrace: show step-by-step counterexample cards
function renderTrace(path) {
const area = document.getElementById('trace-area');
area.innerHTML = '';
path.forEach((state, i) => {
const card = document.createElement('div');
const isBad = state === BAD_STATE;
card.className = 'step-card' + (isBad ? ' bad' : '');
const stepLabel = document.createElement('div');
stepLabel.className = 'step-label';
stepLabel.textContent = fmt('Step %i%', { i: i });
card.appendChild(stepLabel);
const icon = document.createElement('div');
icon.className = 'light-icon';
icon.textContent = STATE_ICON[state] || '?';
card.appendChild(icon);
const name = document.createElement('div');
name.className = 'state-name';
name.textContent = STATE_LABEL[state] || state;
card.appendChild(name);
if (i < path.length - 1) {
const arrow = document.createElement('div');
arrow.className = 'transition';
arrow.textContent = '↓';
card.appendChild(arrow);
}
if (isBad) {
const flag = document.createElement('div');
flag.className = 'transition';
flag.textContent = '⚠ BUG STATE';
card.appendChild(flag);
}
area.appendChild(card);
});
}
function run() {
const k = parseInt(document.getElementById('bound-slider').value, 10);
const box = document.getElementById('result-box');
const txt = document.getElementById('result-text');
const area = document.getElementById('trace-area');
const pre = document.getElementById('formula-pre');
pre.textContent = buildFormulaText(k);
const trace = runBMC(k);
if (trace) {
box.className = 'result-box fail';
txt.textContent = fmt('Counterexample found at step %v% — see trace below.', { v: trace.length - 1 });
renderTrace(trace);
console.log('BMC counterexample trace:', trace.join(' → '));
} else {
box.className = 'result-box ok';
txt.textContent = fmt('No counterexample within k = %k% steps. Try a larger bound.', { k: k });
area.innerHTML = '';
console.log('BMC result: no bug within k =', k);
}
}
document.getElementById('run-btn').addEventListener('click', run);
document.getElementById('reset-btn').addEventListener('click', () => {
document.getElementById('bound-slider').value = 3;
document.getElementById('bound-val').textContent = '3';
document.getElementById('result-box').className = 'result-box';
document.getElementById('result-text').textContent = 'Choose a bound and click Run BMC.';
document.getElementById('trace-area').innerHTML = '';
document.getElementById('formula-pre').textContent = '';
console.log('Demo reset.');
});
document.getElementById('bound-slider').addEventListener('input', function () {
document.getElementById('bound-val').textContent = this.value;
});
<!-- BMC demo: traffic-light controller with one faulty transition -->
<div class="bmc-wrap">
<div class="controls">
<label for="bound-slider">Bound k = <strong id="bound-val">3</strong></label>
<input type="range" id="bound-slider" min="1" max="8" value="3" />
<button id="run-btn" type="button">Run BMC</button>
<button id="reset-btn" type="button" class="ghost">Reset</button>
</div>
<div class="result-box" id="result-box">
<span class="result-text" id="result-text">Choose a bound and click Run BMC.</span>
</div>
<div class="trace-area" id="trace-area"></div>
<details class="formula-details">
<summary>Show propositional formula</summary>
<pre class="formula-pre" id="formula-pre"></pre>
</details>
</div>
/* Bounded Model Checking demo — traffic-light styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.bmc-wrap { display: flex; flex-direction: column; gap: .8rem; }
/* control row */
.controls { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
label { font-size: .9rem; }
input[type=range] { flex: 1; min-width: 120px; }
button { font: 600 14px system-ui; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
/* result banner */
.result-box { border-radius: 8px; padding: .5rem .9rem; font-weight: 600; font-size: .95rem;
background: #e8eef3; border: 1px solid #cdd9e3; min-height: 2.4rem;
display: flex; align-items: center; }
.result-box.ok { background: #d4edda; border-color: #68b37c; color: #0a5c23; }
.result-box.fail { background: #f8d7da; border-color: #e27b85; color: #8b1c26; }
/* counterexample trace cards */
.trace-area { display: flex; flex-wrap: wrap; gap: .5rem; }
.step-card { border: 1px solid #cdd9e3; border-radius: 8px; padding: .5rem .7rem;
min-width: 90px; text-align: center; background: #f4f7fa; }
.step-card.bad { background: #fde8ea; border-color: #e27b85; }
.step-card .step-label { font-size: .72rem; color: #666; margin-bottom: .2rem; }
.step-card .light-icon { font-size: 1.6rem; line-height: 1; }
.step-card .state-name { font-size: .78rem; font-weight: 600; margin-top: .15rem; }
.step-card .transition { font-size: .7rem; color: #555; margin-top: .18rem; }
/* formula display */
.formula-details { font-size: .82rem; }
.formula-details summary { cursor: pointer; color: #1d3557; font-weight: 600; margin-bottom: .3rem; }
.formula-pre { background: #f0f4f8; border: 1px solid #cdd9e3; border-radius: 6px;
padding: .6rem .8rem; overflow-x: auto; white-space: pre-wrap; color: #333;
font-size: .78rem; line-height: 1.5; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Bounded Model Checking demo — traffic-light transition system
// System: 5 states, one faulty transition green->bug (red+green simultaneously).
// All states in the system
const STATES = ['red', 'red_yellow', 'green', 'yellow', 'bug'];
// Human-readable state labels
const STATE_LABEL = {
red: 'Red',
red_yellow: 'Red+Yellow',
green: 'Green',
yellow: 'Yellow',
bug: 'Red+Green (BUG)'
};
const STATE_ICON = {
red: '🔴', red_yellow: '🟡🔴', green: '🟢', yellow: '🟡', bug: '🔴🟢'
};
// Transition relation: maps each state to its successor states
// The faulty transition green->bug represents an illegal hardware state.
const TRANSITIONS = {
red: ['red_yellow'],
red_yellow: ['green'],
green: ['yellow', 'bug'],
yellow: ['red'],
bug: []
};
const INIT_STATE = 'red';
const BAD_STATE = 'bug';
// runBMC(k): BFS up to depth k looking for a path to BAD_STATE
// Returns the path if found, null if no counterexample within k steps.
function runBMC(k) {
// BFS queue: each entry is the path taken so far
const queue = [['red']];
while (queue.length > 0) {
const path = queue.shift();
const last = path[path.length - 1];
if (last === BAD_STATE) return path;
if (path.length - 1 >= k) continue;
for (const next of (TRANSITIONS[last] || [])) {
queue.push([...path, next]);
}
}
return null;
}
// buildFormulaText: display the propositional encoding for the current k
function buildFormulaText(k) {
const lines = [];
lines.push('Initial condition:');
lines.push(' s_0 = red');
lines.push('');
lines.push('Transition constraints (for each i in 0..k-1):');
for (const [src, dsts] of Object.entries(TRANSITIONS)) {
if (dsts.length > 0) {
lines.push(' s_i = ' + src + ' → s_{i+1} ∈ {' + dsts.join(', ') + '}');
}
}
lines.push('');
lines.push('BMC query (find BAD within k steps):');
lines.push(' s_0 = red ∧ T(s_0,s_1) ∧ ... ∧ T(s_{k-1},s_k) ∧ (s_0=bug ∨ ... ∨ s_k=bug)');
lines.push('');
lines.push(fmt('Bound: k = %k%', { k: k }));
lines.push(fmt('Variables: %k%%v% × |STATES| = %v2% boolean variables', { k: k, v: 1, v2: (k + 1) * STATES.length }));
return lines.join('\n');
}
// renderTrace: show step-by-step counterexample cards
function renderTrace(path) {
const area = document.getElementById('trace-area');
area.innerHTML = '';
path.forEach((state, i) => {
const card = document.createElement('div');
const isBad = state === BAD_STATE;
card.className = 'step-card' + (isBad ? ' bad' : '');
const stepLabel = document.createElement('div');
stepLabel.className = 'step-label';
stepLabel.textContent = fmt('Step %i%', { i: i });
card.appendChild(stepLabel);
const icon = document.createElement('div');
icon.className = 'light-icon';
icon.textContent = STATE_ICON[state] || '?';
card.appendChild(icon);
const name = document.createElement('div');
name.className = 'state-name';
name.textContent = STATE_LABEL[state] || state;
card.appendChild(name);
if (i < path.length - 1) {
const arrow = document.createElement('div');
arrow.className = 'transition';
arrow.textContent = '↓';
card.appendChild(arrow);
}
if (isBad) {
const flag = document.createElement('div');
flag.className = 'transition';
flag.textContent = '⚠ BUG STATE';
card.appendChild(flag);
}
area.appendChild(card);
});
}
function run() {
const k = parseInt(document.getElementById('bound-slider').value, 10);
const box = document.getElementById('result-box');
const txt = document.getElementById('result-text');
const area = document.getElementById('trace-area');
const pre = document.getElementById('formula-pre');
pre.textContent = buildFormulaText(k);
const trace = runBMC(k);
if (trace) {
box.className = 'result-box fail';
txt.textContent = fmt('Counterexample found at step %v% — see trace below.', { v: trace.length - 1 });
renderTrace(trace);
console.log('BMC counterexample trace:', trace.join(' → '));
} else {
box.className = 'result-box ok';
txt.textContent = fmt('No counterexample within k = %k% steps. Try a larger bound.', { k: k });
area.innerHTML = '';
console.log('BMC result: no bug within k =', k);
}
}
document.getElementById('run-btn').addEventListener('click', run);
document.getElementById('reset-btn').addEventListener('click', () => {
document.getElementById('bound-slider').value = 3;
document.getElementById('bound-val').textContent = '3';
document.getElementById('result-box').className = 'result-box';
document.getElementById('result-text').textContent = 'Choose a bound and click Run BMC.';
document.getElementById('trace-area').innerHTML = '';
document.getElementById('formula-pre').textContent = '';
console.log('Demo reset.');
});
document.getElementById('bound-slider').addEventListener('input', function () {
document.getElementById('bound-val').textContent = this.value;
});