Sequential Calculator
A signed four-function calculator built in hardware on an FPGA. Add, subtract, multiply, and divide, entered on switches and shown on seven-segment displays, driven by a datapath and a hand-designed controller state machine.
1.0 Summary
This is a calculator with no processor under it. There is no CPU running a program: the arithmetic is done directly by a datapath of adders, registers, and shifters, sequenced by a finite state machine I designed by hand in Verilog. You enter a number on the switches, press an operation, enter the next number, and the machine walks through the states that carry it out.
It runs on an Altera DE2 board. Add and subtract are a single cycle each; multiply and divide are multi-cycle sequences the controller steps through, which is where the real work was.
2.0 Datapath & Controller
The design splits the classic way, into a datapath and a controller. The datapath holds the state and the arithmetic: an accumulator, an adder/subtractor, a Booth multiplier register, division registers, and the number-format converters. The controller is a state machine that does no math itself; it only asserts the control lines that tell the datapath what to do each cycle, and chooses the next state from the current one and the buttons pressed.
That separation is what keeps a machine this size understandable. Every arithmetic component is passive and always computing; the FSM decides which result gets latched into which register, and when.
3.0 Number Representation
Numbers are entered in signed-magnitude, which is what a person reading switches expects: a sign bit and a magnitude. But arithmetic is far easier in two's complement, where subtraction is just addition of a negated value. So the datapath converts on the way in and back on the way out.
// input arrives in signed-magnitude on the switches;
// the datapath works in two's complement, and the display wants SM back
SM2TC #(.width(W)) SM2TC1(Number, NumberTC); // SM -> TC (operand)
TC2SM #(.width(W)) TC2SM1(A, D_SM, DOvf); // TC -> SM (for the 7-seg) Getting this boundary right matters everywhere downstream. Multiply and divide reason about magnitudes and signs separately, so the converters and the sign bookkeeping have to be exactly consistent or a result comes out negated.
4.0 The Controller
The controller is around two dozen states. A small core handles power-on, clear, loading a number, showing a result, and a stuck overflow state. From there each operation has its own branch of states: add and subtract are short, multiply and divide are loops that iterate. The transition table is the part of the project you actually design, and simulating it in ModelSim to watch the state walk is how it gets debugged.
4.1 Multiply: Booth's Algorithm
Multiplication uses Booth's algorithm, which multiplies signed numbers directly by looking at two bits of the multiplier at a time and deciding to add, subtract, or do nothing, then shifting. The controller is those decisions:
XMCheck: begin
if (~PM[1] & PM[0]) X_Next <= XMAdd; // 01 -> add multiplicand
else if ( PM[1] & ~PM[0]) X_Next <= XMSub; // 10 -> subtract
else X_Next <= XMNext; // 00 / 11 -> shift only
end The product accumulates in a combined product-multiplier register that is seeded, folded, and arithmetic-shifted each pass. Using an arithmetic shift, not a logical one, is what preserves the sign through the loop:
// the product/multiplier register PM, updated each Booth step
PM <= LD_M ? {ZERO, A, 1'b0} // seed: multiplicand into the high half
: LD_P ? {PSgn, R, PM[W:0]} // fold the add/sub result back in
: PM_ASR ? PM >>> 1 // arithmetic shift right, one bit/step
: PM; 4.2 Divide: Subtract & Count
Division is done by repeated subtraction. The controller subtracts the divisor from the running remainder and counts, and the count is the quotient. It stops when the remainder drops below the divisor, and the result's sign is the exclusive-or of the two operands' signs.
XDCheck: // restoring division
if (D < ABS_N_SM) X_Next <= XDDone; // remainder < divisor -> stop
else X_Next <= XDSub; // else subtract and count
// the quotient is just how many times we subtracted
QCounter <= (X == XDSub) ? QCounter + 1 : QCounter;
QSgn <= (X == XDLoadN) ? (A[W-1] ^ N_SM[W-1]) : QSgn; // sign = XOR of signs 5.0 Overflow & Display
A result that will not fit in the machine's bit width has to be caught, not shown wrong. When multiply or add overflows, the controller jumps to a dedicated overflow state and stays there, lighting an indicator, until the user clears it. The rest of the time the two's-complement result is converted back to signed-magnitude and driven onto the seven-segment displays, with the operand echoed on a second set as you type it.
6.0 Takeaway
The lasting lesson was the datapath-controller discipline: keep the math passive and always computing, and let one state machine decide what gets latched into which register, and when. It is the same shape as a real CPU's control unit, at a size you can hold in your head.