Altifigence Academy

6 / 20 · Lab

Lab: a signed MAC processing element

Use a standalone model to check valid operands, stalls and clear.

Run a one-PE contract

Save this as pe-lab.mjs and run node pe-lab.mjs with Node.js. It uses no external packages and prints PE lab passed. This checks an arithmetic/control model; it does not compile SystemVerilog or demonstrate execution in DDS. Think of the ordinary two-term job as W=8, K=2, AW=17, while the model uses wide integers for its reference.

The PE receives separate A and B valid flags. It accumulates only when both are true. Forwarding follows each operand's own valid flag. A later lesson adds tags to ensure that two valid operands belong to the same reduction term.

javascript
import assert from 'node:assert/strict';
const pe = {acc:0,a:0,b:0,av:false,bv:false};
function edge({a=0,b=0,av=false,bv=false,
               advance=true,clear=false,reset=false}={}) {
  if (reset || clear) {
    Object.assign(pe,{acc:0,a:0,b:0,av:false,bv:false});
  } else if (advance) {
    if (av && bv) pe.acc += a*b;
    Object.assign(pe,{a,b,av,bv});
  }
  return {...pe};
}
edge({clear:true});
assert.equal(edge({a:-2,b:3,av:true,bv:true}).acc,-6);
const held={...pe};
assert.deepEqual(edge({a:99,b:99,av:true,bv:true,advance:false}),held);
assert.equal(edge({a:4,b:-5,av:true,bv:true}).acc,-26);
assert.equal(edge({a:8,b:8,av:true,bv:false}).acc,-26);
assert.equal(pe.av,true); assert.equal(pe.bv,false);
edge({clear:true,a:100,b:100,av:true,bv:true});
assert.equal(pe.acc,0); assert.equal(pe.av,false);
assert.equal(edge({a:-128,b:-128,av:true,bv:true}).acc,16384);
edge({reset:true,advance:false});
assert.deepEqual(pe,{acc:0,a:0,b:0,av:false,bv:false});
console.log('PE lab passed');

Predict and challenge the result

The first pair produces −6. A pause ignores the offered 99 values and holds all state. The next valid pair contributes −20, reaching −26. The unpaired A token forwards, but contributes no MAC. Clear wins over the simultaneous valid pair; minimum×minimum then gives 16384. Reset clears valid state even when advance is false.

Translate the same priority into always_ff using explicitly signed product and addend signals. When modeling an array, read old state and write a separate next state. Updating PE objects in place can accidentally let a token cross multiple PEs during one edge.

Change the accumulation condition from AND to OR. The unpaired-token assertion must fail. Also try applying advance only to the accumulator while allowing forwarding to move. Explain which contract each assertion protects; a passing message alone is not the full learning outcome.

Record offered operands separately from consumed operands: the paused values of ninety-nine are visible at the interface but never consumed. The wrapper must retain the next scheduled term until advance resumes. Returned state copies preserve earlier observations. Inspect forwarding valid flags as well as the accumulator when translating these checks into a waveform-based testbench.

Actual RTL: the signed MAC PE

Save the attachments signed_mac_pe.sv, systolic_2x2.sv and tb_systolic_2x2.sv together. The first two files are synthesizable RTL; the testbench is simulation-only. The PE sign-extends its full 2W-bit product into AW before accumulation. W=8,K=4 checks use AW=18 for 65536, −65024 and 64516. Ordered k tags reject mismatched terms through protocol_error.

The priority is reset, clear, then advance. Clear finishes before the first data edge. Observe the updated accumulator after each rising edge; neighbors consume registered forwarding on the next advance edge. A pause holds every value, valid flag, tag and count. One job runs between clears, without output quantization or DDS integration.

With Icarus Verilog installed, run:

bash
iverilog -g2012 -s tb_systolic_2x2 -o systolic.vvp signed_mac_pe.sv systolic_2x2.sv tb_systolic_2x2.sv
vvp systolic.vvp

Expect RTL PASS:; failures terminate with $fatal. Modify addend to use zero extension and reproduce the negative-matrix failure. Explain the first mismatching PE accumulator, restore sign extension and rerun. Retain separate Node and HDL logs.

Try it yourself

Insert a valid pair (0,7) between (−2,3) and (4,−5). What is the sum, and how does a valid zero differ from an invalid token?

Read the explanation

The sum is −26. A valid zero still consumes a real k term and advances term counting and tags. An invalid token consumes no term. For a three-term job, update K, width analysis and completion control accordingly.

Lesson files

pe-lab.mjs ↓signed_mac_pe.sv ↓systolic_2x2.sv ↓tb_systolic_2x2.sv ↓

Your choice applies to this browser. Change it any time using the footer.