nmigen-learning/fpga/idea_nmain.md

174 lines
6.7 KiB
Markdown
Raw Permalink Normal View History

2020-08-15 02:59:40 +02:00
# 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>