Wednesday, March 27, 2013

Function signatures and parameters

One of the things that is bothering me for quite a while is the fact that currently functions do not have type information attached to them. The abs function for example looks like this:

inline function abs(a) -> (((a < 0)?-a:a))

This function is of type inline, which means that its invocation will be replaced by the code it is declaring. The parameters are essentially just placeholders. But what happens when you invoke it with a bit type?

bit<16> a=0xFFFF;
bit<16> b=abs(a);

Well, this will cause an error on the generated code. So those kind of errors can only be discovered after the method has been inlined and the error message that can be produced is rather confusing because the user never sees the generated code. This is what type information is for. In Java and actually most programming languages you need to declare what type a parameter has.

void bla(int a, uint8_t b)

Additionally to be able to produce error messages much earlier, it gives the user a much better idea of what he can put into that method. So why does PSHDL not have this feature? Well, there is a rather strange problem that the width of a type introduces. For example the max method might have the following signature:

uint max(uint a, uint b)

This looks quite intuitive, but what happens when we want to use signed ints? Ok, let's add a second method (and implementation)

uint max(uint a, uint b)
int max(int a, int b)

Fair enough, but what about the variable bit-width ints? Lets make a signature that shows that you want to have any width number.

uint max(uint a, uint b)
int max(int a, int b)
int<> max(int<> a, int<> b)
uint<> max(uint<> a, uint<> b)

Now we have a total of 4 methods that all have the same implementation. One simplification would be to say that uint, which is 32 bit can be cast to uint<> implicitly. After all, array indices are cast from uint<> to uint implicitly as well. So that would reduce the number to 2 methods. Another optimization we could do is to create a "super type" num for primitives that can be compared and used in equations. This would reduce the number of methods to one again:

num<> max(num<> a, num<> b)

The downside however is that num is not of much use outside of method parameters. Introducing it can cause the impression that it could be used elsewhere. The same holds true for the notation of having the diamond width <>. But I think that is something the user might understand. But now that you have the <> width, what happens when no width is specified? Does that means it requires the 32 bit type? This is just calling for disaster when users are forgetting it. Actually you always would want to have the <>. But if it always has to be there, why not leave it away? That would be confusing because the the uint in parameters means something different than in other source parts. Better leave the <> there and throw a warning if it is absent.

Another problem remains though. The width of a and b could be different, it would be nice to be able to constrain that. One example:

uint<T> max(uint<T> a, uint<T> b)

This would constrain the width of the arguments to be the same. Determining whether two width are equal is non trivial. For example the width of one could be 2*P while the other is P*2. From an compiler point of view this is nontrivial, and in reality the expression for a width is likely to be more complex. So lets ignore this idea as well. While it is unfortunate that you may have to write the same function 3 times for all primitive types: uint, int and bit, it is much clearer than any of the other ideas.

No comments:

Post a Comment