A pocket calculator on FPGA

One potato two potatoes
Three potatoes, four!
Five potatoes, six potatoes
Seven potatoes, more!

As a follow up to one of my first FPGA projects Adventures in hardware, part 3 - display and a calculator I wanted to implement a more useful calculator that behaves like the common pocket calculators - you enter a number, choose an operator, enter another number, press another operator or = and see the result.

As my board only features three digit display, it will be slightly impractical and work on numbers from 0 to 999, but this time I wanted to operate it through a 4x4 keypad.

I’ve been stuck on how to properly read the keypad for quite some time - this was planned to be titled Hardware Adventures 5, not 9, after all.

the finished version

I had to relabel some keys with a surgical tape and a marker.

The calculator architecture

The main calculator logic is implemented by a state machine with a few registers:

  • RESULT holding the intermediate result
  • ARG holding the currently
  • DISPLAY holding whatever needs to be displayed on the LCD screen
  • OPERATOR holding the previously entered operator (+-*/)

state diagram

A simplified calculator state machine

Notes on state transitions and register updates:

  • When a digit gets pressed, ARG <= ARG * 10 + digit
  • When an operator gets pressed, OPERATOR becomes one of +-*/
  • on Calculate state:
    • RESULT <= RESULT OPERATOR ARG
    • OPERATOR <= OPERATOR_NEXT
  • DISPLAY register gets updated as required after states display_result and digit_pressed

Required modules

To support the main state machine work we’ll need several other components:

  • keypad poller to debounce the keys
  • keypad encoder to encode the keypad readout into a scancode
  • binary to bcd encoder to convert the internal binary representation for display
  • seven segment encoder to encode a BCD digit to segments
  • seven segment driver to multiplex the three decimal digits over the shared wires
  • integer divider to implement division

Reading a keypad

In a 4x4 matrix keypad the key switches are connected by a grid of wires arranged in 4 columns and 4 rows. To determine what button is pressed, we need to scanning the crossings of the rows and columns by activating each column one at a time and read back the status of the rows. There are several articles that elaborate on this topic, if you’re interested.

Using this information we can write a module that encodes a set of 4-bit row and column pins into a hexadecimal keycode.

Keypad layout:

 1 2 3 A          
 4 5 6 B    
 7 8 9 C    
 * 0 # D	

I decided to encode the * and # keys as 0xE and 0xF.

As I wanted to use this for a calculator, I’ve repurposed the C key for “Clear”, then applied a surgical tape to label the +-*/ keys.

We also need to enable builtin pull-down resistors on the row pins in order to ensure a known state (logical zero) when a button is not pressed instead of a floating input.

Entering multiple digits and displaying the number

Let’s represent the number internally as binary and just convert to the display using a BCD encoder.

The reading of the keys will be handled by the calculator state machine within its state_read_digit and state_digit_pressed states.

We can limit reading numbers larger than 999 by using a simple condition in the state_digit_pressed state:

if(reg_arg < 16'd100) 
begin
    reg_arg <= reg_arg * 10 + keypad_out;
end

To display the DISPLAY register we initially convert the 10-digit number to BCD, then encode each digit (ones, tens, hundreds) into bits for the seven-segment display and finally multiplex them to the display.

A common algorithm for binary to BCD conversion is Double dabble and I’ve adapted a Verilog single-clock implementation into a 10-bit version.

Reading the keypad

Although I’ve found multiple descriptions for a single button debouncer, I didn’t understand how to do it over multiple possible columns that we scan and I attempted to insert some kind of debounce circuit running at a lower frequency after the keypad decoder, hoping it would settle on a decoded number - somehow it didn’t.

What finally helped was this assignment from a Tampere University that described the key poller and debouncer for the students. It describes an algorithm that probes the column successively, and for each column it waits for an input.

I could make the wait and hold times configurable, so it can be tuned to a specific keypad.

Decoding the keypad

This is quite straightforward Verilog implementation, with nested case statements, that produces a 4-bit hex scancode for every row/column combination.

The calculator state machine

Now we just need to implement the top module implementing the state machine mentioned earlier in the article and connecting all the other modules.

Adding, subtracting and multiplying

We can implement the basic operations using straightforward Verilog, which will get synthesized into adders and a hardware multiplier, therefore implementing the operations in a single clock cycle.

if(reg_operator == OP_PLUS) begin
    reg_result <= reg_result + reg_arg;
    state <= state_display_result;
end else if(reg_operator == OP_MINUS) begin
    reg_result <= reg_result - reg_arg;
    state <= state_display_result;
end else if(reg_operator == OP_MULTIPLY) begin
    reg_result <= reg_result * reg_arg;
    state <= state_display_result;

Division

My FPGA tools won’t synthesize the / operator, as a single-clock division by a variable number would be impractical to implement. I needed to implement a division module and wire it into the project.

The simplest algorithm of all is division by repeated subtraction:

while N = D do
  N := N - D
  Q := Q + 1
end
R := N
return (Q,R)

This translates to a straightforward Verilog implementation.

Integrating the divider module

As the division will run for a various number of clocks, we need to signal the parent module somehow that the division is completed:

We assign the divider inputs, signal it to start and transition into a waiting state:

end else begin //OP_DIVIDE
	divider_start <= 1'b1;
	numerator <= reg_result;
	denominator <= reg_arg;
	state <= state_dividing;
end

Then we continuously poll the done signal. If it’s asserted, we move over to the common display result state.

state_dividing:
begin
    divider_start <= 1'b0;
    if(divider_done)
    begin
        reg_result <= quotient;
        state <= state_display_result;
    end
end

In the real world one would probably use Long division as it’s completes in much less clock cycles than a simplistic division by subtraction.

Stupid errors I made along the way

  • I forgot again to connect a top module input to a pin. This will produce a cryptic message in Xilinx ISE and took some time to hunt down.

  • An earlier iteration of the keypad decoder couldn’t distinguish zero from a non-key press. I had to add key_pressed signal.

  • Xilinx ISE doesn’t tell you when you make a typo in wire name, when you wire a module to something that doesn’t exist, for example IO_P4_ROW as IO_DP4_ROW.

  • An earlier iteration of the keypad scanner used a clock that was driven by a counter. This is considered a very bad practice and can result in glitches, results in creating new clock domains (which I don’t really understand yet). The solution is to run the slow logic on the same (fast) clock as everything else, but use a slow enable signal or use a PLL circuitry for clock division.

The video