Choose the number of partial-product rows and watch the Wallace tree collapse them. Each layer applies carry-save adders in parallel: every group of three rows becomes two. The tree keeps reducing until only two rows remain, ready for a final adder.
<!-- Wallace Tree demo — interactive carry-save adder reduction visualization -->
<div class="controls">
<label for="rowCount">Partial-product rows:</label>
<input type="range" id="rowCount" min="3" max="12" value="6" step="1">
<span id="rowVal">6</span>
</div>
<div id="treeViz" class="tree-viz"></div>
<div class="stats" id="stats"></div>
<div class="btns">
<button id="btnAnimate" type="button">Animate reduction</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
/* Styles for the Wallace Tree carry-save reduction demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.controls { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; font-size: .9rem; }
.controls label { font-weight: 600; }
#rowCount { flex: 1; max-width: 160px; }
.tree-viz { display: flex; flex-direction: column; gap: 6px; margin: .5rem 0; min-height: 200px; }
.layer { display: flex; flex-direction: column; gap: 3px; }
.layer-label { font-size: .72rem; color: #666; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 1px; }
.row-wrap { display: flex; gap: 2px; align-items: center; }
.bit-box { width: 14px; height: 14px; border-radius: 3px; display: inline-block; }
.bit-sum { background: #3a86ff; }
.bit-carry { background: #ff6b35; }
.bit-input { background: #b0bec5; }
.bit-final { background: #2dc653; }
.row-tag { font-size: .65rem; color: #888; margin-left: 4px; min-width: 32px; }
.arrow { text-align: center; font-size: .8rem; color: #aaa; line-height: 1; margin: 1px 0; }
.stats { font-size: .85rem; background: #f0f4f8; border-radius: 8px; padding: .5rem .7rem; margin: .4rem 0; line-height: 1.6; }
.stats strong { color: #1d3557; }
.btns { display: flex; gap: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.legend { display: flex; gap: .7rem; flex-wrap: wrap; font-size: .75rem; margin-top: .4rem; }
.leg-item { display: flex; align-items: center; gap: 3px; }
/* Wallace Tree demo — watch carry-save adder layers reduce partial products */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const slider = document.getElementById('rowCount');
const rowValEl = document.getElementById('rowVal');
const treeViz = document.getElementById('treeViz');
const statsEl = document.getElementById('stats');
const btnAnimate = document.getElementById('btnAnimate');
const btnReset = document.getElementById('btnReset');
let animTimer = null;
let currentLayers = [];
let animStep = 0;
/* csaReduce: one CSA layer — every group of 3 rows becomes 2 (sum + carry). */
/* This mirrors the hardware where a full adder produces a sum bit and a carry bit. */
function csaReduce(rows) {
/* process groups of three rows, emitting one sum row and one carry row each */
const out = [];
let i = 0;
while (i + 2 < rows.length) {
/* three rows in -> sum row + carry row out (same width, CSA does not widen) */
out.push({ bits: rows[i].bits, type: 'sum' });
out.push({ bits: rows[i].bits, type: 'carry' });
i += 3;
}
/* pass remaining 1 or 2 rows through unchanged */
while (i < rows.length) {
out.push({ bits: rows[i].bits, type: rows[i].type });
i++;
}
return out;
}
/* buildTree(n): construct all reduction layers for n input rows */
function buildTree(n) {
const layers = [];
/* start with n input rows, each n bits wide (simulated; actual widths vary) */
let rows = [];
for (let r = 0; r < n; r++) {
rows.push({ bits: n, type: 'input' });
}
layers.push({ rows: rows.map(r => ({ ...r })), label: i18nFmt('Input: %n% rows', { n: n }) });
/* keep applying CSA layers until only 2 rows remain */
let step = 1;
while (rows.length > 2) {
rows = csaReduce(rows);
layers.push({
rows: rows.map(r => ({ ...r })),
label: i18nFmt('CSA layer %k%', { k: step })
});
step++;
}
/* mark the two remaining rows as final (ready for a ripple-carry adder) */
layers.push({
rows: rows.map(r => ({ ...r, type: 'final' })),
label: 'Final: 2 rows (ripple adder)'
});
return layers;
}
function bitClass(type) {
if (type === 'carry') return 'bit-box bit-carry';
if (type === 'sum') return 'bit-box bit-sum';
if (type === 'final') return 'bit-box bit-final';
return 'bit-box bit-input';
}
function renderLayers(layers, upTo) {
treeViz.innerHTML = '';
for (let li = 0; li < upTo; li++) {
const layer = layers[li];
const div = document.createElement('div');
div.className = 'layer';
const lbl = document.createElement('div');
lbl.className = 'layer-label';
lbl.textContent = layer.label;
div.appendChild(lbl);
layer.rows.forEach(row => {
const wrap = document.createElement('div');
wrap.className = 'row-wrap';
for (let b = 0; b < row.bits; b++) {
const box = document.createElement('span');
box.className = bitClass(row.type);
wrap.appendChild(box);
}
const tag = document.createElement('span');
tag.className = 'row-tag';
tag.textContent = row.type === 'carry' ? 'carry' : (row.type === 'sum' ? 'sum' : '');
wrap.appendChild(tag);
div.appendChild(wrap);
});
treeViz.appendChild(div);
/* draw a downward arrow between layers */
if (li < upTo - 1) {
const arr = document.createElement('div');
arr.className = 'arrow';
arr.textContent = '↓';
treeViz.appendChild(arr);
}
}
}
function renderStats(layers, n) {
const depth = layers.length - 1;
const naiveDepth = Math.max(n - 1, 0);
statsEl.innerHTML =
'<strong>' + i18nFmt('%n% input rows', { n: n }) + '</strong> | ' +
'<strong>' + i18nFmt('%k% CSA layer(s)', { k: depth }) + '</strong> | ' +
'<strong>' + i18nFmt('naive depth: %d%', { d: naiveDepth }) + '</strong>' +
'<br>' + i18nFmt('Depth speedup vs. naive chain: %ratio%×', { ratio: naiveDepth > 0 ? (naiveDepth / depth).toFixed(1) : '—' }) +
'<br><div class="legend">' +
'<span class="leg-item"><span class="bit-box bit-input"></span> input row</span>' +
'<span class="leg-item"><span class="bit-box bit-sum"></span> sum row</span>' +
'<span class="leg-item"><span class="bit-box bit-carry"></span> carry row</span>' +
'<span class="leg-item"><span class="bit-box bit-final"></span> final pair</span>' +
'</div>';
}
function startAnimation() {
if (animTimer) return;
btnAnimate.disabled = true;
animStep++;
if (animStep >= currentLayers.length) {
animStep = currentLayers.length;
btnAnimate.disabled = false;
return;
}
renderLayers(currentLayers, animStep);
animTimer = setTimeout(() => {
animTimer = null;
startAnimation();
}, 420);
}
function reset() {
if (animTimer) { clearTimeout(animTimer); animTimer = null; }
const n = parseInt(slider.value, 10);
currentLayers = buildTree(n);
animStep = 1;
renderLayers(currentLayers, animStep);
renderStats(currentLayers, n);
btnAnimate.disabled = false;
}
slider.addEventListener('input', () => {
rowValEl.textContent = slider.value;
reset();
});
btnAnimate.addEventListener('click', startAnimation);
btnReset.addEventListener('click', reset);
/* kick off with the initial slider value */
reset();
<!-- Wallace Tree demo — interactive carry-save adder reduction visualization -->
<div class="controls">
<label for="rowCount">Partial-product rows:</label>
<input type="range" id="rowCount" min="3" max="12" value="6" step="1">
<span id="rowVal">6</span>
</div>
<div id="treeViz" class="tree-viz"></div>
<div class="stats" id="stats"></div>
<div class="btns">
<button id="btnAnimate" type="button">Animate reduction</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
/* Styles for the Wallace Tree carry-save reduction demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.controls { display: flex; align-items: center; gap: .6rem; margin-bottom: .6rem; font-size: .9rem; }
.controls label { font-weight: 600; }
#rowCount { flex: 1; max-width: 160px; }
.tree-viz { display: flex; flex-direction: column; gap: 6px; margin: .5rem 0; min-height: 200px; }
.layer { display: flex; flex-direction: column; gap: 3px; }
.layer-label { font-size: .72rem; color: #666; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; margin-bottom: 1px; }
.row-wrap { display: flex; gap: 2px; align-items: center; }
.bit-box { width: 14px; height: 14px; border-radius: 3px; display: inline-block; }
.bit-sum { background: #3a86ff; }
.bit-carry { background: #ff6b35; }
.bit-input { background: #b0bec5; }
.bit-final { background: #2dc653; }
.row-tag { font-size: .65rem; color: #888; margin-left: 4px; min-width: 32px; }
.arrow { text-align: center; font-size: .8rem; color: #aaa; line-height: 1; margin: 1px 0; }
.stats { font-size: .85rem; background: #f0f4f8; border-radius: 8px; padding: .5rem .7rem; margin: .4rem 0; line-height: 1.6; }
.stats strong { color: #1d3557; }
.btns { display: flex; gap: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .45; cursor: default; }
.legend { display: flex; gap: .7rem; flex-wrap: wrap; font-size: .75rem; margin-top: .4rem; }
.leg-item { display: flex; align-items: center; gap: 3px; }
/* Wallace Tree demo — watch carry-save adder layers reduce partial products */
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const slider = document.getElementById('rowCount');
const rowValEl = document.getElementById('rowVal');
const treeViz = document.getElementById('treeViz');
const statsEl = document.getElementById('stats');
const btnAnimate = document.getElementById('btnAnimate');
const btnReset = document.getElementById('btnReset');
let animTimer = null;
let currentLayers = [];
let animStep = 0;
/* csaReduce: one CSA layer — every group of 3 rows becomes 2 (sum + carry). */
/* This mirrors the hardware where a full adder produces a sum bit and a carry bit. */
function csaReduce(rows) {
/* process groups of three rows, emitting one sum row and one carry row each */
const out = [];
let i = 0;
while (i + 2 < rows.length) {
/* three rows in -> sum row + carry row out (same width, CSA does not widen) */
out.push({ bits: rows[i].bits, type: 'sum' });
out.push({ bits: rows[i].bits, type: 'carry' });
i += 3;
}
/* pass remaining 1 or 2 rows through unchanged */
while (i < rows.length) {
out.push({ bits: rows[i].bits, type: rows[i].type });
i++;
}
return out;
}
/* buildTree(n): construct all reduction layers for n input rows */
function buildTree(n) {
const layers = [];
/* start with n input rows, each n bits wide (simulated; actual widths vary) */
let rows = [];
for (let r = 0; r < n; r++) {
rows.push({ bits: n, type: 'input' });
}
layers.push({ rows: rows.map(r => ({ ...r })), label: i18nFmt('Input: %n% rows', { n: n }) });
/* keep applying CSA layers until only 2 rows remain */
let step = 1;
while (rows.length > 2) {
rows = csaReduce(rows);
layers.push({
rows: rows.map(r => ({ ...r })),
label: i18nFmt('CSA layer %k%', { k: step })
});
step++;
}
/* mark the two remaining rows as final (ready for a ripple-carry adder) */
layers.push({
rows: rows.map(r => ({ ...r, type: 'final' })),
label: 'Final: 2 rows (ripple adder)'
});
return layers;
}
function bitClass(type) {
if (type === 'carry') return 'bit-box bit-carry';
if (type === 'sum') return 'bit-box bit-sum';
if (type === 'final') return 'bit-box bit-final';
return 'bit-box bit-input';
}
function renderLayers(layers, upTo) {
treeViz.innerHTML = '';
for (let li = 0; li < upTo; li++) {
const layer = layers[li];
const div = document.createElement('div');
div.className = 'layer';
const lbl = document.createElement('div');
lbl.className = 'layer-label';
lbl.textContent = layer.label;
div.appendChild(lbl);
layer.rows.forEach(row => {
const wrap = document.createElement('div');
wrap.className = 'row-wrap';
for (let b = 0; b < row.bits; b++) {
const box = document.createElement('span');
box.className = bitClass(row.type);
wrap.appendChild(box);
}
const tag = document.createElement('span');
tag.className = 'row-tag';
tag.textContent = row.type === 'carry' ? 'carry' : (row.type === 'sum' ? 'sum' : '');
wrap.appendChild(tag);
div.appendChild(wrap);
});
treeViz.appendChild(div);
/* draw a downward arrow between layers */
if (li < upTo - 1) {
const arr = document.createElement('div');
arr.className = 'arrow';
arr.textContent = '↓';
treeViz.appendChild(arr);
}
}
}
function renderStats(layers, n) {
const depth = layers.length - 1;
const naiveDepth = Math.max(n - 1, 0);
statsEl.innerHTML =
'<strong>' + i18nFmt('%n% input rows', { n: n }) + '</strong> | ' +
'<strong>' + i18nFmt('%k% CSA layer(s)', { k: depth }) + '</strong> | ' +
'<strong>' + i18nFmt('naive depth: %d%', { d: naiveDepth }) + '</strong>' +
'<br>' + i18nFmt('Depth speedup vs. naive chain: %ratio%×', { ratio: naiveDepth > 0 ? (naiveDepth / depth).toFixed(1) : '—' }) +
'<br><div class="legend">' +
'<span class="leg-item"><span class="bit-box bit-input"></span> input row</span>' +
'<span class="leg-item"><span class="bit-box bit-sum"></span> sum row</span>' +
'<span class="leg-item"><span class="bit-box bit-carry"></span> carry row</span>' +
'<span class="leg-item"><span class="bit-box bit-final"></span> final pair</span>' +
'</div>';
}
function startAnimation() {
if (animTimer) return;
btnAnimate.disabled = true;
animStep++;
if (animStep >= currentLayers.length) {
animStep = currentLayers.length;
btnAnimate.disabled = false;
return;
}
renderLayers(currentLayers, animStep);
animTimer = setTimeout(() => {
animTimer = null;
startAnimation();
}, 420);
}
function reset() {
if (animTimer) { clearTimeout(animTimer); animTimer = null; }
const n = parseInt(slider.value, 10);
currentLayers = buildTree(n);
animStep = 1;
renderLayers(currentLayers, animStep);
renderStats(currentLayers, n);
btnAnimate.disabled = false;
}
slider.addEventListener('input', () => {
rowValEl.textContent = slider.value;
reset();
});
btnAnimate.addEventListener('click', startAnimation);
btnReset.addEventListener('click', reset);
/* kick off with the initial slider value */
reset();
Notice how the depth grows as O(logn) while the number of rows halves (roughly) at each layer. A naive ripple chain would need n−1 sequential additions; the tree needs only ⌈log3/2n⌉ layers of parallel CSAs.
Comments
Loading comments...