Friday, July 11, 2008

Fixed-Point Arithmetic with Verilog

I'm doing a bit of hardware RTL at the minute, which is a change from my usual testbench code. I'm trying to implement a datapath using fixed-point arithmetic and I'm finding that verilog is not helping me as much as I thought it would. And I've come to realise something...

Fixed-point arithmetic in verilog is broken. And that makes me sad.

Representation

First things first. Let's try to represent a fixed-point number in verilog. What about a vector of bits? Cool, let's say we'll represent our fixed point numbers in N bits:

We'll allow M bits for the integer part and F for the fractional (and, of course, M+F = N).
reg [M+F-1:0] my_number;
OK, so far so good. This is not too self-documenting though - how can you tell that this is a fixed-point number? And where is its binary point? Even worse, you can't tell verilog that the number is supposed to be fixed-point - it doesn't even have a fixed-point 'type'.

Another way is to signal a fixed-point number by having the fractional bits have negative indexes:
reg [M-1:-F] my_number;
For example, an M=1 and F=4 number's vector would be indexed [0:-4]. In this case, we can decree that the binary point is always between indexes 0 and -1, and that any vectors declared with negative indices is a floating point number. This also has the nice property that the value of each bit in the vector is 2index, just like the integer representation.

It's just bookkeeping, though. Nomatter how we spin it, we can't really get verilog to help us out with our fixed-point numbers. For example, say we wanted to add a 1.4 number to a 2.5 - (we'd expect a 3.5 result...):
reg [0:-4] a;
reg [1:-5] b;
reg [3:-5] c;

always @(*) begin
// c = a + b; // this won't line up the binary points for us
c = {a, 1'b0} + b; // we have to make sure that the binary points line up ourselves
end

In this case, Verilog won't line up the binary points for us, it'll line the vectors up LSB to LSB. We're left to make sure that we pad whichever vector to line up the binary points.

Display

Since we can't tell verilog we're working with fixed-point numbers, they're not going to be displayed correctly. Any $displays in the testbench are going to display integers. "But wait!", I hear you say - sure couldn't you just write a function to properly display your fixed-point numbers? Not easily. Functions can't be parameterised (as fair as I'm aware), so you'll have to write conversion functions for each different size of fixed-point number to be displayed. The reason is because slices of vectors must have constant expressions: you can writemy_number[-1:F] if F is a parameter, but not if F is a variable.

In a waveform viewer, our fixed-point numbers are going to be displayed as integers as well. Unless we write expressions (in SimVision, anyway) to convert them.

So, What Now?

To recap, you can't easily work with fixed-point numbers in verilog. Verilog can't help with lining up the binary point for arithmetic, and fixed-point numbers are display incorrectly both in $displays and in waveform viewers.

Should verilog support fixed-point arithmetic? Could you do something with structs and operator overloading in SystemVerilog? (Maybe not for synthesis). It turns out that I don't have solutions or recommendations for any of this, so this was just a rant. Sorry about that...

All this does mean that a lot of the high-level datapath design must be done in Matlab or whatever. I hope Matlab has fixed-point libraries...



An Aside: Text Macros

Why do none of my simulators happily accept the following?

`define two_lsbs(a) a[1:0]
module mess();
reg [3:0] some_vector;
initial begin
$display( "%b", `two_lsbs(some_vector) ); // OK
$display( "%b", `two_lsbs(5) ); // broken
end
endmodule

When you go to use this, simulators complain about unmatched parenthesis when a numeric literal is supplied. Why?


.