we've encountered several situations where simulation goes awry, involving sync.
the following works fine:
class SubModule1:
def elaborate(self, platform):
m.d.sync += self.sync1.eq(self.sync)
m.d.sync += self.sync2.eq(self.sync1)
class MainModule:
def elaborate(self, platform):
m.submodules.sm1 = sm1 = SubModule1()
m.d.comb += self.comb1.eq(sm1.sync2)
However the following does NOT work fine:
class SubModule2:
def elaborate(self, platform):
m.d.comb += self.input.eq(self.sync2)
m.d.comb += self.output.eq(self.input)
class MainModule:
def elaborate(self, platform):
m.submodules.sm1 = sm1 = SubModule1()
m.submodules.sm2 = sm2 = SubModule2()
m.d.comb += sm1.sync2.eq(sm1.sync2)
m.d.comb += self.output.eq(sm2.output)
the 2nd example is detected as a combinatorial loop, and fails simulation. clearly it is NOT a combinatorial loop.
the "fix" is as follows:
class SubModule1:
def elaborate(self, platform):
m.d.sync += self.sync1.eq(self.sync)
m.d.sync += temp_sync2.eq(self.sync1)
m.d.comb += self.sync2.eq(temp_sync2)
when that "trick" is deployed, where the "public API" is in a combinatorial variable instead of accessing the sync'd object, the "problem" goes away.
it is critical to note that it is only when "chained" sync objects are used - a sync on a sync on a sync - followed by then connecting that sync-sync-sync'd output as the input to a second module - that we get problems.