wip
This commit is contained in:
@@ -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
173
fpga/idea_nmain.md
Normal 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
123
fpga/modules/bad_apple.dg
Normal 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
|
||||
BIN
fpga/modules/data/bad_apple.bin
Normal file
BIN
fpga/modules/data/bad_apple.bin
Normal file
Binary file not shown.
24
fpga/spiflash_ideas.md
Normal file
24
fpga/spiflash_ideas.md
Normal 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
0
fpga/stdio/i2c.py
Normal file
0
fpga/stdio/i2s.py
Normal file
0
fpga/stdio/i2s.py
Normal file
1
fpga/stdio/rom.py
Normal file
1
fpga/stdio/rom.py
Normal file
@@ -0,0 +1 @@
|
||||
# spi flash
|
||||
0
fpga/stdio/spi.py
Normal file
0
fpga/stdio/spi.py
Normal file
Reference in New Issue
Block a user