-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Right way to express a bundle of Signals? #211
Comments
Note that passing Should your |
This is essentially correct.
I'm afraid there's no way to do this, because there is no concept of "a module/fragment where the signal was defined" in nMigen. There can't be, for that matter! There's no link between "the constructor where But, if you pass |
Couldn't find a way to define an array of Records in a Record. |
Oh yeah, that's not currently a thing. (Maybe it should be.) |
Hmm, well, what I'm writing is a CPU, and there will be lots and lots and lots of signals that I generally don't want to see. It would likely be just as hard to find the signals I'm interested in among all those other signals. Is there maybe a different thing I should be doing? Maybe making a module holding the formal signals instead and have an interface that sets those signals rather than setting the signals themselves in the various modules making up the CPU? |
This seems like a decent workaround to me. |
Will try it, thanks! |
Can we get examples of the use of |
@RobertBaruch Have you looked at |
They don't seem to explain how From inspection, it seems that |
Ah, I think I have a self-contained example. from nmigen import *
from nmigen.cli import main
from nmigen.asserts import *
from nmigen.hdl.rec import *
cableLayout = Layout([
("data", 8, DIR_FANIN),
("select1", 1, DIR_FANOUT),
("select2", 1, DIR_FANOUT),
])
class CableBundle(Record):
def __init__(self):
super().__init__(cableLayout)
class Memory(Elaboratable):
def __init__(self, selection, value):
self.cable = CableBundle()
self.selection = selection
self.value = value
def elaborate(self, platform):
m = Module()
if self.selection == 1:
selector = self.cable.select1
else:
selector = self.cable.select2
with m.If(selector):
m.d.comb += self.cable.data.eq(self.value)
with m.Else():
m.d.comb += self.cable.data.eq(0)
return m
class HelloRecords(Elaboratable):
def __init__(self):
self.cable = CableBundle()
self.data = Signal(8)
def elaborate(self, platform):
m = Module()
m.submodules.mem1 = mem1 = Memory(1, 0x42)
m.submodules.mem2 = mem2 = Memory(2, 0xBA)
m.d.comb += self.cable.connect(mem1.cable, mem2.cable)
m.d.comb += self.data.eq(self.cable.data)
return m
from nmigen.back import pysim
from nmigen.hdl.ast import Tick
if __name__ == "__main__":
m = Module()
sync = ClockDomain()
m.submodules.hello = hello = HelloRecords()
m.domains.sync = sync
main(m, ports=[hello.data, hello.cable.select1, hello.cable.select2])
with pysim.Simulator(m, vcd_file=open("hello_records.vcd", "w")) as sim:
sim.add_clock(1e-9)
def process():
yield hello.cable.select1.eq(0)
yield hello.cable.select2.eq(0)
yield Tick()
yield hello.cable.select1.eq(1)
yield Tick()
yield hello.cable.select1.eq(0)
yield hello.cable.select2.eq(0)
yield Tick()
yield hello.cable.select2.eq(1)
yield Tick()
yield hello.cable.select1.eq(0)
yield hello.cable.select2.eq(0)
yield Tick()
sim.add_process(process())
sim.run() |
Gotcha. I'll make sure to improve that documentation. The basic idea is that... imagine you are wiring up a bunch of control/status registers, for example. They would all be on a single bus, and each endpoint bus would be represented with a csr_layout = Layout([
("addr", 16, DIR_FANOUT),
("r_data", 32, DIR_FANIN),
("r_en", 1, DIR_FANOUT),
("w_data", 32, DIR_FANOUT),
("w_en", 1, DIR_FANOUT),
])
class Register(Elaboratable):
def __init__(self, addr):
self.bus = Record(csr_layout)
self.addr = addr
self.data = Signal(32)
def elaborate(self, platform):
m = Module()
with m.If(self.bus.addr == self.addr):
with m.If(self.bus.r_en):
m.d.comb += self.bus.r_data.eq(self.data)
with m.If(self.bus.w_en):
m.d.sync += self.data.eq(self.bus.w_data)
return m
class CPU(Elaboratable):
def __init__(self, regsters):
self.registers = registers
def elaborate(self, platform):
csr_bus = Record(csr_layout)
m = Module()
m.d.comb += [
csr_bus.connect(reg.bus for reg in self.registers),
csr_bus.addr.eq(...),
...
] In this case, address and control signals are routed out to every register unmodified; write data is also routed to every register; and read data is routed from every register using a tree of OR'd signals. This maps nicely to a LUT-based architecture because it is cheaper than using multiplexers. However, it requires each device on the bus to keep every DIR_FANIN field "undriven" (that is, at zero), which is fortunately the default reset value, so simply not doing any combinatorial assignment to the bus endpoint does the trick. |
Got it, makes sense. Thanks! |
My use case is that for formal verification, I need to define a big bundle of Signals which are used for keeping track of state. This bundle goes to many modules. Some modules will set some of those signals, and other modules will set other signals. Kind of like this:
This is fine, but in
top_thing.vcd
,EmbeddedThing
'sthing
signal ends up listed under thebottom
module. I can only imagine that's because the first timething
appears in the LHS in the AST is inbottom
.Rather than have
EmbeddedThing
's signals scattered all over the various modules that happen to set them, I'd prefer to have the signals all in one place, preferablytop
, which is where the signals were defined.So (a) am I defining a bundle of Signals correctly and using them correctly, and (b) how do I get the signals all in one place?
The text was updated successfully, but these errors were encountered: