Research · November 4, 2026 · 4 min read
Why we’re called Bitbang
Four Arduino Nanos, two wires per link, and a matrix multiplication that ran about 300 times slower than on one board. Our name comes from the code that cost us that time.
Lab Lead
BiCSL stands for Bitbang Computer Science Lab. The name comes from these ten lines of Arduino code:
void sendByte(byte clk, byte dat, byte v) {
for (int i = 7; i >= 0; i--) {
digitalWrite(clk, LOW);
digitalWrite(dat, (v >> i) & 1);
delayMicroseconds(CLK_HALF);
digitalWrite(clk, HIGH);
delayMicroseconds(CLK_HALF);
}
digitalWrite(clk, LOW);
}
Bit-banging means sending data by setting pins from software, one bit at a time, with no communication hardware doing the work for you. CLK_HALF is 50 µs here, so one bit takes 100 µs and one byte takes 800 µs.
Before this lab had a name, we used that function to link four Arduino Nanos into a small systolic array. The array moved 25% less data than a single board and ran about 300 times slower. Almost all of that time went into the link.
The experiment
We built a 2×2 grid of processing elements (PEs), one board each, joined by two wires per link: clock and data. A fifth board ran the same job sequentially as the control. Both computed , with of size and of size , and both matched the correct answer at every before we timed anything.
| Moves, single board | Moves, array | Time, single board (µs) | Time, array (µs) | Slowdown | |
|---|---|---|---|---|---|
| 2 | 16 | 12 | 52.0 | 16,972.4 | 326× |
| 4 | 32 | 24 | 112.0 | 33,070.4 | 295× |
| 6 | 48 | 36 | 160.0 | 49,216.8 | 308× |
| 8 | 64 | 48 | 208.0 | 65,464.0 | 315× |
A move is one matrix element going from one place to another. The array needs fewer because values get reused as they pass from PE to PE, and that part behaved as the theory says.
The timing did not. Raja (2024) gives , which is for our grid: 6 to 12 cycles. We measured 271,558 to 1,047,424 clock cycles. The formula counts a transfer between PEs as one cycle. One byte over our link took 12,800.
Where the time went
A straight line fits each setup closely:
The array is worse on both slope and intercept, so no value of lets it catch up. Dividing each slope by the moves per gives the price of one move: about 3.2 µs on the single board and about 1,347 µs in the array, a ratio of about 418. With 25% fewer moves, , which is the ratio of the two slopes.
Here is what one step of costs in the array:
| Part | Time (µs) | Share |
|---|---|---|
| Sending bits (five bytes on the critical path) | 4,000 | 49.5% |
| Skew wait | 4,000 | 49.5% |
| Everything else, including the multiply-accumulate | 81 | 1.0% |
The skew wait is a fixed delay of our own. It guarantees that PE(1,1) hears from above before it hears from the left, so it never has to poll. A real array staggers its inputs by a single clock instead, and ours paid for the shortcut with half its run time.
Google's first TPU put a 256×256 array on one chip, where a transfer between neighbours fits inside a clock cycle. We replaced that wire with sendByte. The data reuse survived and the speed did not, which shows how much a systolic design depends on neighbours being cheap to reach.
Nothing catches a dropped bit
No hardware buffers a bit-banged signal. If the receiving code is busy when a clock edge arrives, that bit is gone. Three bugs from the build, all still described in the code comments:
- 255 read as 254. The end marker went out right after the last data byte. PE(1,1) was still handling that byte and missed the first bit. A 20 ms gap fixed it.
- The LCD ate the first transfer. Writing to the display over I2C took several milliseconds, and PE(0,1)'s first byte arrives about 1.6 ms after the start signal. The display now updates only at the end of a run.
- A clock pulse that never happened. Resetting a sender left its clock pin floating, the receiver's pull-up took it high, and the receiver counted a bit. It now resets its bit counter whenever the clock stays low for more than 2 ms.
We picked the name because this is the level we like to work at: close enough to the signal that a missing bit is ours to explain.
Limits of these numbers
- The control imitates a bottleneck in software. The ATmega328P has a modified Harvard architecture, so we routed accesses through a shared variable and counted logical moves, not physical memory accesses.
- A move on the single board is a variable access inside the chip. A move in the array is external communication. The 25% figure shows data reuse and says nothing about speed.
- The grid is fixed at 2×2, so only changed. These results say nothing about adding more PEs.
- The slowdown describes this build, not systolic arrays in general.
The full method and data are in the paper: Von Neumann Bottleneck and Systolic Array Architecture: An Empirical Efficiency Comparison.