A RISC-V processor, and the one instruction that made it 32× faster.
I designed a 4-stage pipelined RV32I CPU with caches and bypassing, then extended its instruction set with packmul, a packed 4×INT8 multiply-accumulate, and rewrote a neural network's inner loop around it.
00At a glance
Mine
Pipeline control for all four stages: stalls, annulment, bypassing and branch redirect. Instruction decoder, ALU, shifter and adder. The direct-mapped cache. Thepackmul instruction end to end, and the rewritten inference loop in C.Course-provided
Project skeleton and type definitions, memory models, the test programs, the trained network and its weights, a software reference for the packed multiply, and the synthesis flow.01The problem
The assignment was open-ended: make a processor run a quantized neural network as fast as possible, where "fast" is clock period multiplied by cycle count, and area is watched. The workload is an INT8 multilayer perceptron for MNIST (784-300-100-10), which is 266,200 multiply-accumulates per image.
The base RV32I instruction set has no multiply. On the first logged run every one of those 8-bit products went through a software shift-and-add routine, and one image cost 42 million cycles: about 158 cycles for each multiply-accumulate. The pipeline was not the bottleneck. The instruction set was.
02The processor
The CPU is a four-stage pipeline: Fetch, Decode, Execute, Writeback. Instructions and data each go through a 4 KB direct-mapped cache (64 sets of 64-byte lines) in front of main memory; the data cache is write-back. Branches are predicted not-taken and resolved in Execute, so a misprediction costs one cycle when the instruction cache hits.
The interesting part of a pipeline is what happens when instructions depend on each other. My Decode stage checks its source registers against what is in flight and picks one of three outcomes: take the value from Execute, take load data from Writeback, or stall. Step through it:
03The instruction: packmul
A 32-bit register holds four 8-bit values. So one instruction can do four of the network's multiplies and add them up: packmul rd, rs1, rs2 treats rs1 as four unsigned bytes and rs2 as four signed bytes, multiplies lane by lane, and writes the 32-bit sum of the four products. It lives in the ALU as four 9×8-bit signed multipliers feeding a two-level adder, and it is encoded in the R-type space (opcode 0110011, funct7 0000001) so the standard assembler can emit it.
There is a catch. The network's activations are signed bytes with an input offset of +128, and the weights are signed too. Rather than build a signed×signed datapath plus an offset adder per lane, I used an identity: flipping the top bit of a signed byte is the same as adding 128 and reading it as unsigned. One XOR with 0x80808080 converts four activations at once, in the form the multiplier wants. Try it:
With the instruction in place, I rewrote the layer's inner loop in C: step four elements at a time, load activations and weights as whole words instead of bytes, XOR, packmul, accumulate. Four byte loads, four software multiplies and four adds became two word loads and two single-cycle instructions.
04Results
The result is a trade, and I think the honest way to show it is all four numbers together. The final design's clock is 51% slower than where I started. It does not matter: the cycle count fell 48-fold, so the product that counts fell 32-fold, for 7.7% more logic.
05Decisions and trade-offs
06What I took from it
Architecture is accounting. Every choice shows up in cycles, picoseconds or square microns, and the only way to know whether an idea is good is to multiply the columns. The second lesson was about correctness: the signedness of each lane in packmul is the kind of detail that produces a network that is almost right, so I pinned down the arithmetic identity across the whole input range before debugging anything else.
If I continued: retime the branch-target path to recover clock period, measure stall and annulment rates with real counters, and compare a two-way cache against the direct-mapped one.