hdl.dsl: warn on suspicious statements like m.If(~True):.

This pattern usually produces an extremely hard to notice bug that
will usually break a design when it is triggered, but will also be
hidden unless the pathological value of a boolean switch is used.

Fixes #159.
This commit is contained in:
whitequark 2019-08-03 14:00:29 +00:00
parent ab5426ce74
commit ace2b5ff0a
2 changed files with 33 additions and 0 deletions

View file

@ -165,9 +165,21 @@ class Module(_ModuleBuilderRoot, Elaboratable):
self._ctrl_stack.append((name, data)) self._ctrl_stack.append((name, data))
return data return data
def _check_signed_cond(self, cond):
cond = Value.wrap(cond)
bits, sign = cond.shape()
if sign:
warnings.warn("Signed values in If/Elif conditions usually result from inverting "
"Python booleans with ~, which leads to unexpected results: ~True is "
"-2, which is truthful. "
"(Silence this warning with `m.If(x)` → `m.If(x.bool())`.)",
SyntaxWarning, stacklevel=4)
return cond
@contextmanager @contextmanager
def If(self, cond): def If(self, cond):
self._check_context("If", context=None) self._check_context("If", context=None)
cond = self._check_signed_cond(cond)
src_loc = tracer.get_src_loc(src_loc_at=1) src_loc = tracer.get_src_loc(src_loc_at=1)
if_data = self._set_ctrl("If", { if_data = self._set_ctrl("If", {
"tests": [], "tests": [],
@ -190,6 +202,7 @@ class Module(_ModuleBuilderRoot, Elaboratable):
@contextmanager @contextmanager
def Elif(self, cond): def Elif(self, cond):
self._check_context("Elif", context=None) self._check_context("Elif", context=None)
cond = self._check_signed_cond(cond)
src_loc = tracer.get_src_loc(src_loc_at=1) src_loc = tracer.get_src_loc(src_loc_at=1)
if_data = self._get_ctrl("If") if_data = self._get_ctrl("If")
if if_data is None: if if_data is None:

View file

@ -268,6 +268,26 @@ class DSLTestCase(FHDLTestCase):
) )
""") """)
def test_If_signed_suspicious(self):
m = Module()
with self.assertWarns(SyntaxWarning,
msg="Signed values in If/Elif conditions usually result from inverting Python "
"booleans with ~, which leads to unexpected results: ~True is -2, which is "
"truthful. (Silence this warning with `m.If(x)` → `m.If(x.bool())`.)"):
with m.If(~True):
pass
def test_Elif_signed_suspicious(self):
m = Module()
with m.If(0):
pass
with self.assertWarns(SyntaxWarning,
msg="Signed values in If/Elif conditions usually result from inverting Python "
"booleans with ~, which leads to unexpected results: ~True is -2, which is "
"truthful. (Silence this warning with `m.If(x)` → `m.If(x.bool())`.)"):
with m.Elif(~True):
pass
def test_Switch(self): def test_Switch(self):
m = Module() m = Module()
with m.Switch(self.w1): with m.Switch(self.w1):