174 lines
6.7 KiB
Markdown
174 lines
6.7 KiB
Markdown
|
# 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
|
||
|
- It should have the ability to synth+PnR a design and program it
|
||
|
- No more using `platform.build(top, do_program=True)` **instead** of `main()`
|
||
|
- 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` in `platform.build`)
|
||
|
- at least print a list of the supported override environ variables
|
||
|
- Have the ability to run a simulation of the design
|
||
|
- Perhaps with the ability to chose among multiple simulation processes/functions provided to `main()`?
|
||
|
- 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()`?
|
||
|
|
||
|
## 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:
|
||
|
|
||
|
```python
|
||
|
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?
|
||
|
|
||
|
<details>
|
||
|
<summary>this mess</summary>
|
||
|
|
||
|
```python
|
||
|
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
|
||
|
|
||
|
```
|
||
|
|
||
|
</details>
|
||
|
|
||
|
<details>
|
||
|
<summary>could become </summary>
|
||
|
|
||
|
```python
|
||
|
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
|
||
|
)
|
||
|
|
||
|
```
|
||
|
|
||
|
</details>
|