This commit is contained in:
2020-08-15 02:59:40 +02:00
parent 18bf707fdc
commit 5702101d03
14 changed files with 599 additions and 65 deletions

View File

@@ -3,6 +3,7 @@ import "/nmigen_dg/*"
import "/sys"
import "common/to_signed"
import "modules/vga/DviController12"
import "modules/bad_apple/BadAppleDecoder"
import "stdio/ice40pll/add_domain_from_pll"
import "resources/pmod"
@@ -22,8 +23,35 @@ Top = subclass Elaboratable where
print pll_config
# Feed a picture to the DVI controller
'''
# Feed bad_apple to the DVI controller
apple = Submodule.apple$ BadAppleDecoder!
buffer = Memory
width: 1
depth: apple.size
rdbuffer = Submodule.rdbuffer$ buffer.read_port!
wrbuffer = Submodule.wrbuffer$ buffer.write_port!
Sync$ wrbuffer.en ::= apple.pixel_valid
Sync$ wrbuffer.addr ::= apple.pixel_x + apple.pixel_y*apple.h
Sync$ wrbuffer.data ::= apple.pixel_data
wait = Signal 30 reset:1
Comb$ apple.next ::= 0
When dvi.vblank_start $ ->
Sync$ wait ::= wait.rotate_left 1
Comb$ apple.next ::= wait!!0
Comb$ rdbuffer.addr ::= (dvi.pixel_x >> 3) + (dvi.pixel_y >> 3)*apple.h
pixel = Cat rdbuffer.data rdbuffer.data rdbuffer.data rdbuffer.data
Sync$ dvi.r ::= pixel
Sync$ dvi.g ::= pixel
Sync$ dvi.b ::= pixel
'''
# Feed a picture to the DVI controller
scroll = Signal 8
period = int$ pll_config.achieved / @fps
@@ -32,6 +60,12 @@ Top = subclass Elaboratable where
When (counter==0) $ ->
Sync$ counter ::= int period
Sync$ scroll ::= scroll + 1
asd = Cat
platform.request "led_g" 1
platform.request "led_g" 2
platform.request "led_g" 3
platform.request "led_g" 4
Sync$ asd ::= asd + 1
Sync$ dvi.r ::= 0x0
Sync$ dvi.g ::= 0x0
@@ -56,10 +90,13 @@ Top = subclass Elaboratable where
tx2 ^ ty2 ,->
Sync$ dvi.r ::= 0xF
Sync$ dvi.g ::= 0xF
'''
'''
if __name__ == "__main__" =>
plat = ICEBreakerPlatform!
plat.add_resources$ pmod.dvi_12bit 0
plat.add_resources$ plat.break_off_pmod
plat.build (Top 800 480) do_program: ("--flash" in sys.argv) synth_opts: "-dsp"
#(import "/nmigen/cli/main" pure) (Top 800 480) plat
plat.build (Top 800 480 fps:60) do_program: ("--flash" in sys.argv) synth_opts: "-dsp -abc2 -relut"

173
fpga/idea_nmain.md Normal file
View File

@@ -0,0 +1,173 @@
# 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>

123
fpga/modules/bad_apple.dg Normal file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env -S python3 -m dg
import "/nmigen/cli/main"
import "/nmigen/sim/pysim/Simulator"
import "/nmigen_dg/*"
import "/os/path" qualified
import "../common/deduce_ports"
RleXorPrevDecoder = subclass Elaboratable where
__init__ = w h data ~> None where
@w, @h, @data, @size = w, h, data, w*h
@pixel_x = Signal$ range w # pixel being output
@pixel_y = Signal$ range h # pixel being output
@pixel_data = Signal! # data of pixel being output
@pixel_valid = Signal! # wether pixel being output is valid
@next = Signal! # pulse this to start decoding next frame
elaborate = platform ~> m where with m = Module! =>
# Default output
Sync$ @pixel_valid ::= 0
# Data to decode
data_mem = Memory # TODO: use spi flash instead
width: 8
depth: (len @data)
init: (list$ bytearray$ @data)
rddata = Submodule.rddata$ data_mem.read_port!
# RLE decoder state:
pixel = Signal 1
pixel_prev = Signal 1
n_times = Signal 14
# XOR Frame buffer
frame = Memory
width: 1
depth: @size
rdframe = Submodule.rdframe$ frame.read_port!
wrframe = Submodule.wrframe$ frame.write_port!
offset = Signal$ range @size
Comb$ rdframe.addr ::= offset
Comb$ wrframe.addr ::= offset
Sync$ wrframe.en ::= 0
do_write_pixel = Signal!
pixel_to_write = Signal!
Comb$ do_write_pixel ::= 0 # TODO: needed?
When do_write_pixel $ ->
Sync$ @pixel_data ::= (rdframe.data ^ pixel_to_write)
Sync$ wrframe.data ::= (rdframe.data ^ pixel_to_write)
Sync$ @pixel_valid ::= 1
Sync$ wrframe.en ::= 1
Sync$ offset ::= offset + 1 # needs to start at -1
Sync$ @pixel_y ::= @pixel_y + 1
When (@pixel_y == @h - 1) $ ->
Sync$ @pixel_y ::= 0
Sync$ @pixel_x ::= @pixel_x + 1
# RLE & XOR Decoder
FSM
DECODED ->
When @next $ ->
Sync$ offset ::= -1 # max int
Sync$ @pixel_y ::= -1 # max int
Sync$ @pixel_x ::= 0
DECODED |>. READ
READ ->
Sync$ pixel ::= rddata.data !! 7
Sync$ n_times ::= rddata.data !! slice 0 6 # TODO: proper slicing syntax would be nice
Sync$ rddata.addr ::= rddata.addr + 1
When
rddata.data !! 6 ,-> READ |>. READ_MORE
otherwise ,-> READ |>. WRITE_PIXELS_BEGIN
READ_MORE ->
Sync$ n_times ::= ((n_times << 8) | rddata.data)
Sync$ rddata.addr ::= rddata.addr + 1
READ_MORE |>. WRITE_PIXELS_BEGIN
WRITE_PIXELS_BEGIN ->
is_first = offset == 0
has_changed = pixel != pixel_prev
When (~has_changed & ~is_first) $ -> # there was an omitted n_values=1 in between
Comb$ pixel_to_write ::= ~pixel
Comb$ do_write_pixel ::= 1
Sync$ pixel_prev ::= pixel
WRITE_PIXELS_BEGIN |>. WRITE_PIXELS
WRITE_PIXELS ->
Comb$ pixel_to_write ::= pixel
Comb$ do_write_pixel ::= 1
Sync$ n_times ::= n_times - 1
When (n_times == 1) $ -> When # == 0 next cycle
offset == @size - 1 ,-> WRITE_PIXELS |>. DECODED
otherwise ,-> WRITE_PIXELS |>. READ
BadAppleDecoder = -> RleXorPrevDecoder 85 64 bad_apple_data where
fname = os.path.join
os.path.dirname __file__
"data"
"bad_apple.bin"
with f = open fname "rb" =>
bad_apple_data = f.read!
bad_apple_data = bad_apple_data !! slice 0 ((len bad_apple_data) // 58) # TODO: use full movie when we've moved data to spi flash
print ((len bad_apple_data) // 58)
#bad_apple_data = b"0000"
if __name__ == "__main__" =>
design = RleXorPrevDecoder 85 64 b"1234"
ports = deduce_ports design
main design
name: "bad_apple"
ports: ports

Binary file not shown.

24
fpga/spiflash_ideas.md Normal file
View File

@@ -0,0 +1,24 @@
# Ability to provide binary blobs to also be flashed to the FPGA's SPI flash
Whenever a SPI flash module is added to `nmigen-stdio` or wherever,
there should be an option to define binary blobs to be programed/flashed
along with the synthesized bitfile.
These binary blobs could either be added in `if __main__ == ...`, or possibly even during elaboration. Example:
```python
with open("myfile.bin", "rv") as f:
platform.add_spi_flash_data(start_addr=0x020000, label="riscv_program", data=f.read())
# 'data' could be optional for RTL generation, but required for building/programming
# perhaps an 'end_addr' or 'size' kwarg could be needed?
...
def elaborate(self, platform):
start_addr, size = platform.get_spi_flash_data(label="riscv_rom") # throws if the label is not defined
```
# Request for Comment:
Different FPGAs are programmed differently. The Ice40 usually write to the SPI flash when programmed,
but the arty 7 is only programed in RAM, without neccesarily writing to the SPI flash.

0
fpga/stdio/i2c.py Normal file
View File

0
fpga/stdio/i2s.py Normal file
View File

1
fpga/stdio/rom.py Normal file
View File

@@ -0,0 +1 @@
# spi flash

0
fpga/stdio/spi.py Normal file
View File