6.7 KiB
The current nmigen.cli.main()
can:
- Can display a help text when asked (not done by default)
- Has the ability to elaborate a design
- Has the ability to simulate the design for x cycles with no input
I've worked out a few use-cases i've seen others have, and ones i've run into myself:
What could we want form a new main()
?
-
It should display the help text by default (better discoverability)
-
It should have the ability to elaborate the design
- Perhaps provide the ability to set design parameters from the CLI
- Ability to choose among multiple target platforms provided to
main()
- Perhaps even provide a mocked/generic platform instead of
None
, with arbitrary clocks and resources available
- Perhaps even provide a mocked/generic platform instead of
-
It should have the ability to synth+PnR a design and program it
- No more using
platform.build(top, do_program=True)
instead ofmain()
- It should provide the option to program in the CLI interface, instead of hardcoding it
- "program" may also differ between platform. Options to just program in RAM or to write to the SPI flash could also be needed
- Have the option to print the list of files generated
- It should provide the option to print the synthesis and PnR output, or silence it
- Have the ability to avoid rebuilding the bitfile, and instead just flash the current one on disk
- Have the ability to override the default "build/" folder from the CLI
- Have the ability to set overrides (the
kwargs
inplatform.build
)- at least print a list of the supported override environ variables
- No more using
-
Have the ability to run a simulation of the design
- Perhaps with the ability to chose among multiple simulation processes/functions provided to
main()
?
- Perhaps with the ability to chose among multiple simulation processes/functions provided to
-
Give the user the ability to provide binary blobs to be flashed to the SPI flash (see #)
-
It could be extendable with plugins
- A plugin system could extend all nmigen design, instead of requiring the author to add the plugin
-
Have the ability to run a simulation of the design
- Perhaps with the ability to chose among multiple simulation processes/functions provided to
main()
?
- Perhaps with the ability to chose among multiple simulation processes/functions provided to
Should a new main
function be accessible from python?
If you publish your design as a python module, it should
be possible to just import
it and convert it to verilog/ilang.
This culd be useful when using systems like litex
.
Therefore i suggest the a new main
have a pattern like this:
main = nmigen.main(MyDesign(), ...)
if __name__ == "__main__":
main.run_cli()
, making a main
object accessible from within python.
Should a new main
function provide an interface to run tests?
Testing is currently done through unittest
or pytest
. There is however the issue
of not immediately knowing which test_*.py file covers the module you're currently looking at.
How does the current "simulate" action differ from tests?
Assert()
is currently being added to the simulator (see #427), blurring the line
between a pure simulation and a unit test. Should the "simulate" action be renamed? Merged? Leave it as is?
Should we change and break the old main()
, or make a new one
Even though the backend API is not declared fully stable yet, breaking old code
sucks. The current main
could therefore be deprecated and a new one then be added in
a different location or under a different name. (nmain
is cute)
While on the topic of platform overrides:
The ability to set platform overrides during elaboration could be usefull: PLL module for example would then be able to add the proper timing constraints. (Vivado is able to infer these constraints, but nextpnr do not to my knowledge)
How could such a new main
look like?
this mess
class MyModule(Elaboratable):
def __init__(self, param: int):
self.param = param
self.out = Signal()
...
# the largest issue is the lack of a consistent CLI interface
# to expect from a given nmigen .py file
if __name__ == "__main__":
top = MyModule(param=4) # `param` is hardcoded. This may also result in a UnusedElaboratable warning
if sys.argv[1] = "build":
if sys.argv[2] = "icebreaker":
plat = ICEBreakerPlatform()
plat.add_resources(...)
elif sys.argv[2] = "fomu"
plat = FomuHackerPlatform()
plat.add_resources(...)
else:
exit(1)
# `do_program` is usually hard-coded.
# The folder "build" should also be configurable from CLI
plat.build(top, do_program=True)
elif sys.argv[1] == "test": # "simulate" is already taken by main()
# logic for multiple simulations to select from is manually made
sim = Simulator(top)
def my_proc():
yield ...
sim.add_sync_process(my_proc)
with sim.write_vcd("uart.vcd", "uart.gtkw"): # hardcoded filenames, never printed to user
sim.run()
else:
main(top, ports=[ top.out ]) # '-h' doesn't mention the two modes defined above
could become
class MyModule(Elaboratable):
def __init__(self, param):
self.param = param
self.out = Signal()
...
if __name__ == "__main__":
platforms, simulations = [], [] # using lists makes the nMain interface more transparent
# supporting multiple platforms encourages designing the
# top-level module in a platform-agnostic fashion
@platforms.append
def icebreaker(): # nMain infers the name from function.__name__
plat = ICEBreakerPlatform()
plat.add_resources(...)
return plat
@platforms.append
def fomu():
plat = FomuHackerPlatform()
plat.add_resources(...)
return plat
@simulations.append
def sim_the_foo(sim): # sim = Simulator(design)
sim.add_clock(1e-6)
@sim.add_sync_process
def my_proc():
yield ...
# `sim` could then be automatically run by nMain, with configurable wavefile output
nMain(
name = "blinker", # Current default is "top", perhaps default to design.__name__ instead?
design = MyModule, # Pass a class itself instead of an instance, avoids UnusedElaboratable warnings
design_kwargs = {"param": 4} # Optional, should be possible to override in CLI
ports = lambda x: [ x.out ], # Optional due to platform.request() being a thing.
# Perhaps all Signals defined in __init__ could be inferred as ports
# by inspecting it with dir() and getattr() before calling .elaborate()?
platforms = platforms, # Optional
simulations = simulations, # Optional
)