From 8477698d77be5b4834a3ac07b4b667a5a0067f79 Mon Sep 17 00:00:00 2001 From: saji Date: Sat, 28 Sep 2024 17:18:09 -0500 Subject: [PATCH] add rgbview with channel_slice operator --- src/groovylight/common.py | 22 ++++++++++++++- src/groovylight/hub75.py | 41 +++++++--------------------- src/groovylight/tests/test_common.py | 23 ++++++++++++++++ src/groovylight/tests/test_hub75.py | 16 +++++++---- 4 files changed, 64 insertions(+), 38 deletions(-) create mode 100644 src/groovylight/tests/test_common.py diff --git a/src/groovylight/common.py b/src/groovylight/common.py index 894dc28..6a34c49 100644 --- a/src/groovylight/common.py +++ b/src/groovylight/common.py @@ -1,4 +1,4 @@ -from amaranth import Array, unsigned +from amaranth import unsigned, Cat from amaranth.lib import wiring, data from amaranth.lib.wiring import Out @@ -13,6 +13,9 @@ class RGBLayout(data.StructLayout): } ) + def __call__(self, value): + return RGBView(self, value) + Rgb888Layout = RGBLayout(8, 8, 8) Rgb666Layout = RGBLayout(6, 6, 6) @@ -20,6 +23,23 @@ Rgb666Layout = RGBLayout(6, 6, 6) Rgb111Layout = RGBLayout(1, 1, 1) +class RGBView(data.View): + + def channel_size(self) -> int: + return self.red.shape() + def channel_slice(self, bit: int) -> Rgb111Layout: + """Select bits from each channel and use it to form an Rgb111Layout. + This is useful for BCM stuff, since the bits are sliced to form a bitplane. + """ + return Rgb111Layout( + Cat( + self.red.bit_select(bit, 1), + self.green.bit_select(bit, 1), + self.blue.bit_select(bit, 1), + ) + ) + + class Hub75Stream(wiring.Signature): """A Hub75E Driver for a single string of panels.""" diff --git a/src/groovylight/hub75.py b/src/groovylight/hub75.py index 730d68b..4b6096f 100644 --- a/src/groovylight/hub75.py +++ b/src/groovylight/hub75.py @@ -122,29 +122,16 @@ class Hub75DataDriver(wiring.Component): 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), - ) + ram_rgb_slice = self.bram.data.channel_slice(self.bcm_select) else: + m.d.comb += self.bram.addr.eq(pixnum) 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), - ), + self.bram.data[0].channel_slice(self.bcm_select), + self.bram.data[1].channel_slice(self.bcm_select), ] ) - m.d.comb += self.bram.addr.eq(pixnum) - with m.FSM(): with m.State("init"): m.d.sync += [ @@ -215,7 +202,7 @@ class Hub75StringDriver(wiring.Component): "bcm_select": In(3), "done": Out(1), "start": In(1), - "bram_port": In( + "bram": In( ReadPort.Signature( addr_width=ceil_log2(panel_length), shape=data.ArrayLayout(Rgb666Layout, 2), @@ -232,16 +219,8 @@ class Hub75StringDriver(wiring.Component): self._counter = counter = Signal(32) # unused count is optimized out m.d.sync += counter.eq(counter + 1) - ram_rgb0_slice = Cat( - self.bram_port.data[0]["red"].bit_select(self.bcm_select, 1), - self.bram_port.data[0]["blue"].bit_select(self.bcm_select, 1), - self.bram_port.data[0]["green"].bit_select(self.bcm_select, 1), - ) - ram_rgb1_slice = Cat( - self.bram_port.data[1]["red"].bit_select(self.bcm_select, 1), - self.bram_port.data[1]["blue"].bit_select(self.bcm_select, 1), - self.bram_port.data[1]["green"].bit_select(self.bcm_select, 1), - ) + ram_rgb0_slice = self.bram.data[0].channel_slice(self.bcm_select) + ram_rgb1_slice = self.bram.data[1].channel_slice(self.bcm_select) m.d.comb += [ self.display_out.rgb0.eq(ram_rgb0_slice), self.display_out.rgb1.eq(ram_rgb1_slice), @@ -249,7 +228,7 @@ class Hub75StringDriver(wiring.Component): m.d.sync += Assert(self.bcm_select < 6) pixnum = Signal(range(self.panel_length), init=self.panel_length - 1) - m.d.comb += self.bram_port.addr.eq(pixnum) + m.d.comb += self.bram.addr.eq(pixnum) with m.FSM(): with m.State("init"): @@ -259,7 +238,7 @@ class Hub75StringDriver(wiring.Component): pixnum.eq(self.panel_length - 1), ] with m.If(self.start == 1): - m.d.sync += self.bram_port.en.eq(1) + m.d.sync += self.bram.en.eq(1) m.next = "writerow" with m.State("writerow"): with m.If(counter[0] == 0): @@ -272,7 +251,7 @@ class Hub75StringDriver(wiring.Component): with m.Else(): m.next = "done" with m.State("done"): - m.d.sync += [self.done.eq(1), self.bram_port.en.eq(0)] + m.d.sync += [self.done.eq(1), self.bram.en.eq(0)] m.next = "init" return m diff --git a/src/groovylight/tests/test_common.py b/src/groovylight/tests/test_common.py new file mode 100644 index 0000000..a180063 --- /dev/null +++ b/src/groovylight/tests/test_common.py @@ -0,0 +1,23 @@ +from amaranth import unsigned +import pytest + +from groovylight.common import Rgb888Layout, Rgb666Layout, RGBView + + +def test_rgbview(): + rgb = Rgb888Layout(0xAABBCC) + + assert rgb.channel_size() == unsigned(8) + + rgb18 = Rgb666Layout(0x2DEFD) + + slice = rgb.channel_slice(1) + assert isinstance(slice, RGBView), "channel_slice should return another rgbview" + + assert slice.channel_size() == unsigned(1), "channel_slice channel size should be 1" + assert isinstance( + rgb18.channel_slice(5), RGBView + ), "channel_slice should return another rgbview" + + with pytest.raises(ValueError, match="Target of a view is 0 bit"): + rgb.channel_slice(8) diff --git a/src/groovylight/tests/test_hub75.py b/src/groovylight/tests/test_hub75.py index 2469af1..837466e 100644 --- a/src/groovylight/tests/test_hub75.py +++ b/src/groovylight/tests/test_hub75.py @@ -4,9 +4,14 @@ from amaranth.lib.memory import Memory from amaranth.sim import Simulator import pytest -from groovylight.common import Rgb888Layout +from groovylight.common import Rgb888Layout, Rgb666Layout + +from groovylight.hub75 import ( + Hub75Coordinator, + Hub75DataDriver, + Hub75StringDriver, +) -from ..hub75 import Hub75Coordinator, Hub75DataDriver, Hub75StringDriver, Rgb666Layout def test_stringdriver(): @@ -21,7 +26,7 @@ def test_stringdriver(): ) port = mem.read_port() - wiring.connect(m, port, dut.bram_port) + wiring.connect(m, port, dut.bram) async def testbench(ctx): # select a bit, strobe start, read values, test against known. @@ -29,7 +34,7 @@ def test_stringdriver(): ctx.set(dut.start, 1) await ctx.tick() ctx.set(dut.start, 0) - assert ctx.get(dut.bram_port.en) == 1 + assert ctx.get(dut.bram.en) == 1 pass sim = Simulator(m) @@ -57,8 +62,7 @@ def test_datadriver(): ctx.set(dut.start, 1) await ctx.tick() ctx.set(dut.start, 0) - assert ctx.get(dut.bram_port.en) == 1 - pass + assert ctx.get(dut.bram.en) == 1 sim = Simulator(m) sim.add_clock(1e-6)