generated from saji/ecp5-template
Compare commits
3 commits
0c1d924166
...
95ca2447cb
Author | SHA1 | Date | |
---|---|---|---|
Saji | 95ca2447cb | ||
Saji | 5ae230ac00 | ||
Saji | 2c88d73ded |
44
pdm.lock
44
pdm.lock
|
@ -41,7 +41,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "basedpyright"
|
||||
version = "1.17.5"
|
||||
version = "1.18.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "static type checking for Python (but based)"
|
||||
groups = ["dev"]
|
||||
|
@ -49,8 +49,8 @@ dependencies = [
|
|||
"nodejs-wheel-binaries>=20.13.1",
|
||||
]
|
||||
files = [
|
||||
{file = "basedpyright-1.17.5-py3-none-any.whl", hash = "sha256:7ea23951be3620fe4638f6f51625a3d5a65a57ce004e836c608058610153362b"},
|
||||
{file = "basedpyright-1.17.5.tar.gz", hash = "sha256:8315f5f2fddfdad162513ef9d9a0b24bb00cc862e8883819ae98a3e28472874d"},
|
||||
{file = "basedpyright-1.18.0-py3-none-any.whl", hash = "sha256:96238749b870809719d9e99587346056ca68169803095901fc5e028c733c9b54"},
|
||||
{file = "basedpyright-1.18.0.tar.gz", hash = "sha256:82a0cceb7a20bbdd8b801a2ea41a035bc101d94bc634157bcc2ecf0f7aff52f8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -205,27 +205,27 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.6.7"
|
||||
version = "0.6.8"
|
||||
requires_python = ">=3.7"
|
||||
summary = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "ruff-0.6.7-py3-none-linux_armv6l.whl", hash = "sha256:08277b217534bfdcc2e1377f7f933e1c7957453e8a79764d004e44c40db923f2"},
|
||||
{file = "ruff-0.6.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c6707a32e03b791f4448dc0dce24b636cbcdee4dd5607adc24e5ee73fd86c00a"},
|
||||
{file = "ruff-0.6.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:533d66b7774ef224e7cf91506a7dafcc9e8ec7c059263ec46629e54e7b1f90ab"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a86aac6f915932d259f7bec79173e356165518859f94649d8c50b81ff087e9"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3f8822defd260ae2460ea3832b24d37d203c3577f48b055590a426a722d50ef"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba4efe5c6dbbb58be58dd83feedb83b5e95c00091bf09987b4baf510fee5c99"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:525201b77f94d2b54868f0cbe5edc018e64c22563da6c5c2e5c107a4e85c1c0d"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8854450839f339e1049fdbe15d875384242b8e85d5c6947bb2faad33c651020b"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f0b62056246234d59cbf2ea66e84812dc9ec4540518e37553513392c171cb18"},
|
||||
{file = "ruff-0.6.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b1462fa56c832dc0cea5b4041cfc9c97813505d11cce74ebc6d1aae068de36b"},
|
||||
{file = "ruff-0.6.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:02b083770e4cdb1495ed313f5694c62808e71764ec6ee5db84eedd82fd32d8f5"},
|
||||
{file = "ruff-0.6.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c05fd37013de36dfa883a3854fae57b3113aaa8abf5dea79202675991d48624"},
|
||||
{file = "ruff-0.6.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f49c9caa28d9bbfac4a637ae10327b3db00f47d038f3fbb2195c4d682e925b14"},
|
||||
{file = "ruff-0.6.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a0e1655868164e114ba43a908fd2d64a271a23660195017c17691fb6355d59bb"},
|
||||
{file = "ruff-0.6.7-py3-none-win32.whl", hash = "sha256:a939ca435b49f6966a7dd64b765c9df16f1faed0ca3b6f16acdf7731969deb35"},
|
||||
{file = "ruff-0.6.7-py3-none-win_amd64.whl", hash = "sha256:590445eec5653f36248584579c06252ad2e110a5d1f32db5420de35fb0e1c977"},
|
||||
{file = "ruff-0.6.7-py3-none-win_arm64.whl", hash = "sha256:b28f0d5e2f771c1fe3c7a45d3f53916fc74a480698c4b5731f0bea61e52137c8"},
|
||||
{file = "ruff-0.6.7.tar.gz", hash = "sha256:44e52129d82266fa59b587e2cd74def5637b730a69c4542525dfdecfaae38bd5"},
|
||||
{file = "ruff-0.6.8-py3-none-linux_armv6l.whl", hash = "sha256:77944bca110ff0a43b768f05a529fecd0706aac7bcce36d7f1eeb4cbfca5f0f2"},
|
||||
{file = "ruff-0.6.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27b87e1801e786cd6ede4ada3faa5e254ce774de835e6723fd94551464c56b8c"},
|
||||
{file = "ruff-0.6.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd48f945da2a6334f1793d7f701725a76ba93bf3d73c36f6b21fb04d5338dcf5"},
|
||||
{file = "ruff-0.6.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:677e03c00f37c66cea033274295a983c7c546edea5043d0c798833adf4cf4c6f"},
|
||||
{file = "ruff-0.6.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f1476236b3eacfacfc0f66aa9e6cd39f2a624cb73ea99189556015f27c0bdeb"},
|
||||
{file = "ruff-0.6.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f5a2f17c7d32991169195d52a04c95b256378bbf0de8cb98478351eb70d526f"},
|
||||
{file = "ruff-0.6.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5fd0d4b7b1457c49e435ee1e437900ced9b35cb8dc5178921dfb7d98d65a08d0"},
|
||||
{file = "ruff-0.6.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8034b19b993e9601f2ddf2c517451e17a6ab5cdb1c13fdff50c1442a7171d87"},
|
||||
{file = "ruff-0.6.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cfb227b932ba8ef6e56c9f875d987973cd5e35bc5d05f5abf045af78ad8e098"},
|
||||
{file = "ruff-0.6.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef0411eccfc3909269fed47c61ffebdcb84a04504bafa6b6df9b85c27e813b0"},
|
||||
{file = "ruff-0.6.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:007dee844738c3d2e6c24ab5bc7d43c99ba3e1943bd2d95d598582e9c1b27750"},
|
||||
{file = "ruff-0.6.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ce60058d3cdd8490e5e5471ef086b3f1e90ab872b548814e35930e21d848c9ce"},
|
||||
{file = "ruff-0.6.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1085c455d1b3fdb8021ad534379c60353b81ba079712bce7a900e834859182fa"},
|
||||
{file = "ruff-0.6.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:70edf6a93b19481affd287d696d9e311388d808671bc209fb8907b46a8c3af44"},
|
||||
{file = "ruff-0.6.8-py3-none-win32.whl", hash = "sha256:792213f7be25316f9b46b854df80a77e0da87ec66691e8f012f887b4a671ab5a"},
|
||||
{file = "ruff-0.6.8-py3-none-win_amd64.whl", hash = "sha256:ec0517dc0f37cad14a5319ba7bba6e7e339d03fbf967a6d69b0907d61be7a263"},
|
||||
{file = "ruff-0.6.8-py3-none-win_arm64.whl", hash = "sha256:8d3bb2e3fbb9875172119021a13eed38849e762499e3cfde9588e4b4d70968dc"},
|
||||
{file = "ruff-0.6.8.tar.gz", hash = "sha256:a5bf44b1aa0adaf6d9d20f86162b34f7c593bfedabc51239953e446aefc8ce18"},
|
||||
]
|
||||
|
|
|
@ -14,6 +14,7 @@ class RGBLayout(data.StructLayout):
|
|||
)
|
||||
|
||||
|
||||
Rgb888Layout = RGBLayout(8, 8, 8)
|
||||
Rgb666Layout = RGBLayout(6, 6, 6)
|
||||
|
||||
Rgb111Layout = RGBLayout(1, 1, 1)
|
||||
|
|
|
@ -86,9 +86,14 @@ class DisplayDimensions:
|
|||
height: int
|
||||
mux: int = 2 # number of lines driven at once.
|
||||
|
||||
@property
|
||||
def addr_bits(self) -> int:
|
||||
return ceil(log2(self.height / self.mux))
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
return self.length * self.height
|
||||
|
||||
|
||||
class DisplayRotation(Enum):
|
||||
"""Display rotation enums. The names indicate the general direction
|
||||
|
@ -162,13 +167,25 @@ class DisplayGeometry:
|
|||
self.strict = strict
|
||||
pass
|
||||
|
||||
def add_string(self, s: DisplayString):
|
||||
"""Add a new string to the display. This new string is located at
|
||||
a specific point, and has a direction, along with dimension that reveal
|
||||
the number of address lines (typically 64, with 1:32 selection so 5 address
|
||||
bits) and the total length of the string which is used to size the line
|
||||
buffers.
|
||||
@property
|
||||
def n_strings(self) -> int:
|
||||
"""Returns the number of strings in the display"""
|
||||
return len(self._strings)
|
||||
|
||||
@property
|
||||
def framebuffer_size(self) -> int:
|
||||
"""Returns the total size of the display, in pixels.
|
||||
|
||||
NOTE: This currently only works with strict=true.
|
||||
"""
|
||||
sum = 0
|
||||
for s in self._strings:
|
||||
sum += s.dimensions.size
|
||||
|
||||
return sum
|
||||
|
||||
def add_string(self, s: DisplayString):
|
||||
"""Add a new string to the display.
|
||||
When in strict mode, this method will throw an exception if this new string
|
||||
will overlap with an existing string.
|
||||
"""
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
from amaranth import Module, Cat, Mux, ShapeLike, Signal, Assert
|
||||
from amaranth import Module, Cat, Mux, ShapeLike, Signal, Assert, Array
|
||||
from amaranth.build import Platform
|
||||
from amaranth.lib import wiring, data
|
||||
from amaranth.lib.wiring import In, Out
|
||||
from amaranth.lib.memory import Memory, ReadPort, WritePort
|
||||
from amaranth.utils import ceil_log2
|
||||
|
||||
from .common import Rgb666Layout, Hub75Stream, Hub75Ctrl, Hub75Data
|
||||
from .common import Rgb666Layout, Hub75Stream, Hub75Ctrl, Hub75Data, Rgb888Layout
|
||||
|
||||
|
||||
class SwapBuffer(wiring.Component):
|
||||
|
@ -80,6 +80,128 @@ class SwapBuffer(wiring.Component):
|
|||
return m
|
||||
|
||||
|
||||
class Hub75DataDriver(wiring.Component):
|
||||
def __init__(
|
||||
self,
|
||||
panel_length: int = 128,
|
||||
data_shape=Rgb888Layout,
|
||||
double_fetch=True,
|
||||
*,
|
||||
src_loc_at=0,
|
||||
):
|
||||
self.panel_length = panel_length
|
||||
self.double_fetch = double_fetch
|
||||
# if we're using double_fetch then each bram cell is exactly one color,
|
||||
# if we're doing single fetch, it's 2 colors stacked.
|
||||
bram_shape = data_shape if double_fetch else data.ArrayLayout(data_shape, 2)
|
||||
addr_width = (
|
||||
ceil_log2(panel_length * 2) if double_fetch else ceil_log2(panel_length)
|
||||
)
|
||||
super().__init__(
|
||||
{
|
||||
"bcm_select": In(ceil_log2(data_shape["red"].shape.width)),
|
||||
"done": Out(1),
|
||||
"start": In(1),
|
||||
"bram": In(
|
||||
ReadPort.Signature(
|
||||
addr_width=addr_width,
|
||||
shape=bram_shape,
|
||||
)
|
||||
),
|
||||
"data": Out(Hub75Data()),
|
||||
},
|
||||
src_loc_at=src_loc_at,
|
||||
)
|
||||
|
||||
def elaborate(self, platform: Platform) -> Module:
|
||||
m = Module()
|
||||
|
||||
counter = Signal(32)
|
||||
|
||||
pixnum = Signal(range(self.panel_length), init=self.panel_length - 1)
|
||||
if self.double_fetch:
|
||||
pixrow = Signal(1)
|
||||
m.d.comb += self.bram.addr.eq(Cat(pixrow, pixnum))
|
||||
ram_rgb_slice = Cat(
|
||||
self.bram.data["red"].bit_select(self.bcm_select, 1),
|
||||
self.bram.data["blue"].bit_select(self.bcm_select, 1),
|
||||
self.bram.data["green"].bit_select(self.bcm_select, 1),
|
||||
)
|
||||
else:
|
||||
ram_rgb_slice = Array(
|
||||
[
|
||||
Cat(
|
||||
self.bram.data[0]["red"].bit_select(self.bcm_select, 1),
|
||||
self.bram.data[0]["blue"].bit_select(self.bcm_select, 1),
|
||||
self.bram.data[0]["green"].bit_select(self.bcm_select, 1),
|
||||
),
|
||||
Cat(
|
||||
self.bram.data[1]["red"].bit_select(self.bcm_select, 1),
|
||||
self.bram.data[1]["blue"].bit_select(self.bcm_select, 1),
|
||||
self.bram.data[1]["green"].bit_select(self.bcm_select, 1),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
m.d.comb += self.bram.addr.eq(pixnum)
|
||||
|
||||
with m.FSM():
|
||||
with m.State("init"):
|
||||
m.d.sync += [
|
||||
self.done.eq(0),
|
||||
counter.eq(0),
|
||||
pixnum.eq(pixnum.reset),
|
||||
]
|
||||
if self.double_fetch:
|
||||
m.d.sync += pixrow.eq(0)
|
||||
with m.If(self.start == 1):
|
||||
m.d.sync += self.bram.en.eq(1)
|
||||
m.next = "prefetch" if self.double_fetch else "writerow"
|
||||
with m.State("prefetch"):
|
||||
# TODO: do we need this
|
||||
m.next = "writerow"
|
||||
|
||||
with m.State("writerow"):
|
||||
if self.double_fetch:
|
||||
c = counter[0:1]
|
||||
with m.If(c == 0):
|
||||
m.d.sync += [
|
||||
self.data.rgb0.eq(ram_rgb_slice),
|
||||
pixrow.eq(1),
|
||||
]
|
||||
with m.If(c == 1):
|
||||
m.d.sync += self.data.rgb1.eq(ram_rgb_slice)
|
||||
with m.If(c == 2):
|
||||
pass
|
||||
|
||||
with m.If(c == 3):
|
||||
m.d.sync += [
|
||||
counter.eq(0),
|
||||
pixnum.eq(pixnum - 1),
|
||||
pixrow.eq(0),
|
||||
]
|
||||
with m.If(pixnum == 0):
|
||||
m.next = "done"
|
||||
else:
|
||||
with m.If(counter[0] == 0):
|
||||
m.d.sync += [
|
||||
self.data.rgb0.eq(ram_rgb_slice[0]),
|
||||
self.data.rgb1.eq(ram_rgb_slice[1]),
|
||||
]
|
||||
with m.If(counter[0] == 1):
|
||||
m.d.sync += [
|
||||
pixnum.eq(pixnum - 1),
|
||||
counter.eq(0),
|
||||
]
|
||||
with m.If(pixnum == 0):
|
||||
m.next = "done"
|
||||
with m.State("done"):
|
||||
m.d.sync += [self.done.eq(1), self.bram.en.eq(0)]
|
||||
m.next = "init"
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class Hub75StringDriver(wiring.Component):
|
||||
"""A data driver for Hub75 panels. This accesses the line memory and feeds out the data.
|
||||
It is controlled by a Hub75Coordinator to signal when it should run and what bit of the data
|
||||
|
@ -126,7 +248,7 @@ class Hub75StringDriver(wiring.Component):
|
|||
]
|
||||
m.d.sync += Assert(self.bcm_select < 6)
|
||||
|
||||
pixnum = Signal(ceil_log2(self.panel_length), init=self.panel_length - 1)
|
||||
pixnum = Signal(range(self.panel_length), init=self.panel_length - 1)
|
||||
m.d.comb += self.bram_port.addr.eq(pixnum)
|
||||
|
||||
with m.FSM():
|
||||
|
@ -180,11 +302,12 @@ class Hub75Coordinator(wiring.Component):
|
|||
donearr = []
|
||||
startStrings = Signal(1)
|
||||
stringsDone = Signal(1)
|
||||
|
||||
for i in range(self.n_strings):
|
||||
sb = SwapBuffer(depth=128, shape=data.ArrayLayout(Rgb666Layout, 2))
|
||||
bufs += sb
|
||||
bufs.append(sb)
|
||||
stringdriver = Hub75StringDriver(128)
|
||||
strings += stringdriver
|
||||
strings.append(stringdriver)
|
||||
wiring.connect(m, sb.read_port, stringdriver.bram_port)
|
||||
m.d.comb += [
|
||||
self.data[i].eq(stringdriver.display_out),
|
||||
|
@ -192,12 +315,41 @@ class Hub75Coordinator(wiring.Component):
|
|||
sb.selector.eq(swapline),
|
||||
]
|
||||
m.submodules += [sb, stringdriver]
|
||||
donearr += stringdriver.done
|
||||
donearr.append(stringdriver.done)
|
||||
# combine the done signals into one signal with AND-reduction
|
||||
m.d.comb += stringsDone.eq(Cat(*donearr).all())
|
||||
|
||||
self.addr = Signal(5)
|
||||
# handle the fetch side.
|
||||
# WIP: pass in fetcher/pixgen/geometry.
|
||||
# right now we assume that it's just one panel,
|
||||
# address is (string_number, hi/lo, addr)
|
||||
|
||||
for i in range(self.n_strings):
|
||||
lookup_addr = Cat(i, self.addr, 0)
|
||||
# generate a sequence of transfers.
|
||||
|
||||
with m.FSM():
|
||||
with m.State("init"):
|
||||
# go to preload.
|
||||
pass
|
||||
|
||||
with m.State("preload"):
|
||||
pass
|
||||
|
||||
with m.State("run"):
|
||||
pass
|
||||
|
||||
with m.State("swap"):
|
||||
pass
|
||||
|
||||
return m
|
||||
|
||||
|
||||
# fetch line
|
||||
#
|
||||
|
||||
|
||||
class Hub75EDriver(wiring.Component):
|
||||
"""An optimized driver for hub75 strings.
|
||||
This version is faster than most implementations by merging the exposure
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from amaranth import Array, Module, Cat, Signal, Assert, unsigned
|
||||
from amaranth.build import Platform
|
||||
from amaranth import Module
|
||||
from amaranth.lib import wiring, data
|
||||
from amaranth.lib.wiring import In, Out
|
||||
from amaranth.lib.memory import Memory, WritePort
|
||||
from amaranth.lib.memory import Memory
|
||||
from amaranth.sim import Simulator
|
||||
import pytest
|
||||
|
||||
from ..hub75 import Hub75Coordinator, Hub75StringDriver, Rgb666Layout
|
||||
from groovylight.common import Rgb888Layout
|
||||
|
||||
from ..hub75 import Hub75Coordinator, Hub75DataDriver, Hub75StringDriver, Rgb666Layout
|
||||
|
||||
|
||||
def test_stringdriver():
|
||||
|
@ -38,6 +39,41 @@ def test_stringdriver():
|
|||
sim.run_until(1e-6 * 1000)
|
||||
|
||||
|
||||
def test_datadriver():
|
||||
# the string driver test must
|
||||
# 1. finish
|
||||
# 2. strobe through all of the data in the array
|
||||
# 3. slice the correct bit from the data.
|
||||
m = Module()
|
||||
m.submodules.dut = dut = Hub75DataDriver()
|
||||
m.submodules.mem = mem = Memory(shape=Rgb888Layout, depth=256, init=[])
|
||||
port = mem.read_port()
|
||||
|
||||
wiring.connect(m, port, dut.bram)
|
||||
|
||||
async def testbench(ctx):
|
||||
# select a bit, strobe start, read values, test against known.
|
||||
ctx.set(dut.bcm_select, 5)
|
||||
ctx.set(dut.start, 1)
|
||||
await ctx.tick()
|
||||
ctx.set(dut.start, 0)
|
||||
assert ctx.get(dut.bram_port.en) == 1
|
||||
pass
|
||||
|
||||
sim = Simulator(m)
|
||||
sim.add_clock(1e-6)
|
||||
|
||||
with sim.write_vcd("output.vcd"):
|
||||
sim.run_until(1e-6 * 1000)
|
||||
|
||||
|
||||
@pytest.mark.skip()
|
||||
def test_hub75():
|
||||
m = Module()
|
||||
m.submodules.dut = dut = Hub75Coordinator(1)
|
||||
|
||||
sim = Simulator(m)
|
||||
sim.add_clock(1e-6)
|
||||
|
||||
with sim.write_vcd("output.vcd"):
|
||||
sim.run_until(1e-6 * 1000)
|
||||
|
|
Loading…
Reference in a new issue