UCSBarchlab / PyRTL

A collection of classes providing simple hardware specification, simulation, tracing, and testing suitable for teaching and research. Simplicity, usability, clarity, and extensibility are the overarching goals, rather than performance or optimization.
http://ucsbarchlab.github.io/PyRTL
BSD 3-Clause "New" or "Revised" License
253 stars 76 forks source link

Improve handling of signed numbers #450

Open fdxmw opened 3 months ago

fdxmw commented 3 months ago

It is easy to make confusing mistakes with signed values in PyRTL. A broken example:

import pyrtl

a = -128
b = 127

# Compute a - b, which should be -255.
a_const = pyrtl.Const(name='a', val=a, signed=True, bitwidth=8)
b_const = pyrtl.Const(name='b', val=b, signed=True, bitwidth=8)

diff = a_const - b_const
diff.name = 'diff'

sim = pyrtl.Simulation()
sim.step()

print('expected diff is', a - b)

print('actual diff is',
      pyrtl.val_to_signed_integer(sim.inspect('diff'), bitwidth=diff.bitwidth),
      'bitwidth', diff.bitwidth)

When this example is run, it produces:

expected diff is -255
actual diff is 1 bitwidth 9

Note that the actual output has bitwidth 9, which is sufficient to represent -255.

This example is especially confusing because PyRTL has all the information to do the right thing, because we've marked the Consts as signed=True. To fix this example, the user currently has to call signed_sub instead of using the overloaded - operator.

The current approach is error prone because the user has to keep track of signed-ness as values propagate through their circuit, and make sure they call functions like signed_add and val_to_signed_integer at the right times.

It seems better to track signed-ness within WireVector, or within a WireVector subclass, but this raises questions about type promotion - what happens when we add a signed value and an unsigned value? My current intuition is to match Verilog's promotion behavior, but we should think about this more.

See also #417