When an operation is performed on two Bits object, we need to determine the bitwidth of the resulting value. Verilog has complex (and sometimes unintuitive) rules for how to determine the bitwidth of expressions which depends on both the right-hand side AND left-hand side of an expression.
In PyMTL's Bits we have simpler rules than Verilog which are meant to be more intuitive, but in some cases we aren't doing the right thing. The plan going forward should be to try to match Vivado's ap_uint behavior as much as possible.
Expressions involving unsized contants (indicated using N):
Operation Verilog Chisel PyMTL ap_int/ap_uint
----------- ------------------- ------------------- ---------------- ----------------
i + N max(L(i), 32) @ ? L(i) max(L(i),L(N)) + 1
+ narrower_is_signed(i,N)
i - N max(L(i), 32) @ ? L(i) max(L(i),L(N)) + 1
+ narrower_is_signed(i,N)
i * N max(L(i), 32) @ ? L(i) L(i) + L(N)
i / N max(L(i), 32) @ ? L(i) L(i) + is_signed(N)
i % N max(L(i), 32) @ ? L(i) if (sign(i) == sign(N)): min(L(i),L(N))
elif (is_signed(i)): L(N) + 1
i & N max(L(i), 32) @ ? L(i) max(L(i),L(N))
i | N max(L(i), 32) @ ? L(i) max(L(i),L(N))
i ^ N max(L(i), 32) @ ? L(i) max(L(i),L(N))
i ^~ j max(L(i), 32) @ - - -
i >> N L(i) @@ ? L(i) L(i)
i << N L(i) @@ ? L(i) L(i)
N + j max(32, L(j)) @ ? L(j) max(L(N),L(j)) + 1
+ narrower_is_signed(N,j)
N - j max(32, L(j)) @ ? L(j) max(L(N),L(j)) + 1
+ narrower_is_signed(N,j)
N * j max(32, L(j)) @ ? L(j) L(N) + L(j)
N / j max(32, L(j)) @ ? invalid L(N) + is_signed(j)
N % j max(32, L(j)) @ ? invalid if (sign(N) == sign(j)): min(L(N),L(j))
elif (is_signed(N)): L(j) + 1
N & j max(32, L(j)) @ ? L(j) max(L(N),L(j))
N | j max(32, L(j)) @ ? L(j) max(L(N),L(j))
N ^ j max(32, L(j)) @ ? L(j) max(L(N),L(j))
i ^~ j max(32, L(j)) @ - - -
N >> j L(i) @@ ? invalid L(N)
N << j L(i) @@ ? invalid L(N)
i ? N : k max(32, L(k)) @@ ? ? ?
Definitions in Python-like pseudocode:
def is_signed( x ):
type( x ).is_signed_datatype()
def narrower_is_signed( i, j ):
if L(i) < L(j):
return is_signed( i ) and not is_signed( j )
elif L(i) > L(j):
return is_signed( j ) and not is_signed( i )
else:
return ???
def L( x ):
if type( x, BitType ): return x.nbits
elif type( x, char ): return 8
elif type( x, short ): return 16
elif type( x, int ): return 32
elif type( x, long ): return 32
elif type( x, long long ): return 64
Verilog self-determined and context-determined operations are described
below:
Self-determined (no marker): bitwidths of RHS operands are never extended.
The bitwidth of the RHS expression depends only on the RHS.
Context-determined (@): bitwidths of RHS operands are extended depending
on the bitwidth of other RHS variables and the LHS variable. The bitwidth
of the RHS expression is therefore dependent on all RHS and LHS variables.
Partially context-determined (@@): the bitwdiths of some RHS are extended
depending on the bitwidth of other RHS variables and the LHS variable.
The bitwidth of the RHS expression is therefore depends on some RHS
variables and the LHS variable. In the above table the j variable is
self-determined, but all other variables are context-determined.
When an operation is performed on two Bits object, we need to determine the bitwidth of the resulting value. Verilog has complex (and sometimes unintuitive) rules for how to determine the bitwidth of expressions which depends on both the right-hand side AND left-hand side of an expression.
In PyMTL's Bits we have simpler rules than Verilog which are meant to be more intuitive, but in some cases we aren't doing the right thing. The plan going forward should be to try to match Vivado's ap_uint behavior as much as possible.
Below is a charge describing the behavior of:
References:
Expressions involving only explicitly-sized variables and constants:
Expressions involving unsized contants (indicated using N):
Definitions in Python-like pseudocode:
Verilog self-determined and context-determined operations are described below:
Self-determined (no marker): bitwidths of RHS operands are never extended. The bitwidth of the RHS expression depends only on the RHS.
Context-determined (@): bitwidths of RHS operands are extended depending on the bitwidth of other RHS variables and the LHS variable. The bitwidth of the RHS expression is therefore dependent on all RHS and LHS variables.
Partially context-determined (@@): the bitwdiths of some RHS are extended depending on the bitwidth of other RHS variables and the LHS variable. The bitwidth of the RHS expression is therefore depends on some RHS variables and the LHS variable. In the above table the
j
variable is self-determined, but all other variables are context-determined.