From 66b492e1473bf02af2b866e4ed6eab72366001c8 Mon Sep 17 00:00:00 2001 From: saji Date: Thu, 19 Sep 2024 00:03:19 -0500 Subject: [PATCH] refactor around rgb666 remove 4xclocking for double-fetch architecture (still using 2xclock for better S/H) --- src/groovylight/bitslicer.py | 135 +++++++++++++++-------------- src/groovylight/lineram.py | 12 --- src/groovylight/test_hub75.py | 11 +-- src/groovylight/test_swapbuffer.py | 4 +- 4 files changed, 76 insertions(+), 86 deletions(-) delete mode 100644 src/groovylight/lineram.py diff --git a/src/groovylight/bitslicer.py b/src/groovylight/bitslicer.py index 81d47dd..01d31e5 100644 --- a/src/groovylight/bitslicer.py +++ b/src/groovylight/bitslicer.py @@ -1,9 +1,8 @@ -from amaranth import Array, Module, Cat, Mux, ShapeLike, Signal, Assert, unsigned +from amaranth import Module, Cat, Mux, ShapeLike, Signal, Assert, unsigned 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.sim import Simulator from amaranth.utils import ceil_log2 @@ -18,7 +17,7 @@ class RGBLayout(data.StructLayout): ) -Rgb888Layout = RGBLayout(8, 8, 8) +Rgb666Layout = RGBLayout(6, 6, 6) Rgb111Layout = RGBLayout(1, 1, 1) @@ -113,28 +112,31 @@ class SwapBuffer(wiring.Component): shape=self.data_shape, depth=self.depth, init=[] ) - read0 = self.bram0.read_port() - write0 = self.bram0.write_port() - read1 = self.bram1.read_port() - write1 = self.bram1.write_port() - - # for name, member in self.write_port.signature.members.items(): - # m.d.comb += self.write_port.members[name].eq(Mux(self.selector, write0[name], write1[name])) - # + rd0 = self.bram0.read_port() + wr0 = self.bram0.write_port() + rd1 = self.bram1.read_port() + wr1 = self.bram1.write_port() m.d.comb += [ - write0.addr.eq(self.write_port.addr), - write1.addr.eq(self.write_port.addr), - write0.en.eq(~self.selector), - write1.en.eq(self.selector), - # self.write_port.data.eq(Mux(self.selector, write0.data, write1.data)), - write0.data.eq(self.write_port.data), - write1.data.eq(self.write_port.data), - read0.addr.eq(self.read_port.addr), - read1.addr.eq(self.read_port.addr), - read0.en.eq(~self.selector), - read1.en.eq(self.selector), - self.read_port.data.eq(Mux(self.selector, read1.data, read0.data)), + # wr addres + wr0.addr.eq(self.write_port.addr), + wr1.addr.eq(self.write_port.addr), + # write enables are based on selector + wr0.en.eq(~self.selector), + wr1.en.eq(self.selector), + # connect write data. This is FINE because + # there is one driver (the external writer) + # and we en based on selector so the other one isn't active + wr0.data.eq(self.write_port.data), + wr1.data.eq(self.write_port.data), + # connect rd address lines + rd0.addr.eq(self.read_port.addr), + rd1.addr.eq(self.read_port.addr), + rd0.en.eq(~self.selector), + rd1.en.eq(self.selector), + # we do this because the read_data lines are driven, this prevents + # double-driver situations even though we en using selector above + self.read_port.data.eq(Mux(self.selector, rd1.data, rd0.data)), ] return m @@ -146,33 +148,48 @@ class Hub75StringDriver(wiring.Component): it should send. """ - bcm_select: In(3) - done: Out(1) - start: In(1) - bram_port: In(ReadPort.Signature(addr_width=9, shape=Rgb888Layout)) - display_out: Out(Hub75Data()) # data signal output. - def __init__(self, panel_length: int = 128, *, src_loc_at=0): self.panel_length = panel_length - super().__init__(None, src_loc_at=src_loc_at) + super().__init__( + { + "bcm_select": In(3), + "done": Out(1), + "start": In(1), + "bram_port": In( + ReadPort.Signature( + addr_width=ceil_log2(panel_length), + shape=data.ArrayLayout(Rgb666Layout, 2), + ) + ), + "display_out": Out(Hub75Data()), + }, + src_loc_at=src_loc_at, + ) def elaborate(self, platform: Platform) -> Module: m = Module() - # add two memories - - self._counter = counter = Signal(32) + self._counter = counter = Signal(32) # unused count is optimized out m.d.sync += counter.eq(counter + 1) - ram_rgb_slice = Cat( - self.bram_port.data["red"].bit_select(self.bcm_select, 1), - self.bram_port.data["blue"].bit_select(self.bcm_select, 1), - self.bram_port.data["green"].bit_select(self.bcm_select, 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), + ) + m.d.comb += [ + self.display_out.rgb0.eq(ram_rgb0_slice), + self.display_out.rgb1.eq(ram_rgb1_slice), + ] + m.d.sync += Assert(self.bcm_select < 6) - pixnum = Signal(range(self.panel_length), init=self.panel_length - 1) - pixrow = Signal(1, init=0) - m.d.comb += self.bram_port.addr.eq(Cat(pixrow, pixnum)) + pixnum = Signal(ceil_log2(self.panel_length), init=self.panel_length - 1) + m.d.comb += self.bram_port.addr.eq(pixnum) with m.FSM(): with m.State("init"): @@ -180,42 +197,22 @@ class Hub75StringDriver(wiring.Component): self.done.eq(0), counter.eq(0), pixnum.eq(self.panel_length - 1), - pixrow.eq(0), ] with m.If(self.start == 1): m.d.sync += self.bram_port.en.eq(1) m.next = "writerow" - with m.State("prefetch"): - with m.If(counter == 0): - m.d.sync += pixrow.eq(0) - with m.Elif(counter == 1): - m.d.sync += pixrow.eq(1) - with m.Elif(counter == 2): - m.d.sync += self.display_out.rgb0.eq(ram_rgb_slice) - with m.Elif(counter == 3): - m.d.sync += [self.display_out.rgb1.eq(ram_rgb_slice), counter.eq(0)] - m.next = "writerow" with m.State("writerow"): - with m.If(counter[0:1] == 0): - # rising edge of the clock - m.d.sync += pixrow.eq(0) + with m.If(counter[0] == 0): + # do nothing + pass - with m.If(counter[0:1] == 1): - m.d.sync += [ - pixrow.eq(1), - self.display_out.rgb0.eq(ram_rgb_slice), - ] - with m.If(counter[0:1] == 2): - m.d.sync += [ - pixnum.eq(pixnum - 1), - pixrow.eq(0), - self.display_out.rgb1.eq(ram_rgb_slice), - ] - with m.If(counter == 128 * 2 + 1): + with m.If((counter[0] == 1) & (pixnum != 0)): + m.d.sync += pixnum.eq(pixnum - 1) + with m.Else(): m.next = "done" with m.State("done"): - m.d.sync += self.done.eq(1) + m.d.sync += [self.done.eq(1), self.bram_port.en.eq(0)] m.next = "init" return m @@ -239,6 +236,10 @@ class Hub75EDriver(wiring.Component): This version is faster than most implementations by merging the exposure period and the data-write period to happen simultaneously. As a result, the display can be brighter due to higher duty cycle. + + + NOTICE: this is a direct port of the old verilog code. It isn't up to date with the + modified structure. Notably, it uses RGB888 with double-fetch (4xclocking) """ start: In(1) diff --git a/src/groovylight/lineram.py b/src/groovylight/lineram.py deleted file mode 100644 index 9a3e363..0000000 --- a/src/groovylight/lineram.py +++ /dev/null @@ -1,12 +0,0 @@ -from amaranth import Module, Cat, Signal, Assert, unsigned -from amaranth.build import Platform - -from amaranth.lib import wiring, data -from amaranth.lib.wiring import In, Out -from amaranth.lib.memory import Memory -# file to wrap line ram using PDPw16KD -# this is tricky. - - -def lineram(): - return Memory(shape=unsigned(24), depth=512) diff --git a/src/groovylight/test_hub75.py b/src/groovylight/test_hub75.py index bbf8bb6..79c3a18 100644 --- a/src/groovylight/test_hub75.py +++ b/src/groovylight/test_hub75.py @@ -5,17 +5,19 @@ from amaranth.lib.wiring import In, Out from amaranth.lib.memory import Memory, WritePort from amaranth.sim import Simulator -from .bitslicer import Hub75StringDriver, Rgb888Layout +from .bitslicer import Hub75StringDriver, Rgb666Layout def test_stringdriver(): - # the string driver test must + # 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 = Hub75StringDriver() - m.submodules.mem = mem = Memory(shape=Rgb888Layout, depth=512, init=[]) + m.submodules.mem = mem = Memory( + shape=data.ArrayLayout(Rgb666Layout, 2), depth=128, init=[] + ) port = mem.read_port() wiring.connect(m, port, dut.bram_port) @@ -26,12 +28,11 @@ 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_port.en) == 1 pass sim = Simulator(m) sim.add_clock(1e-6) with sim.write_vcd("output.vcd"): - sim.run_until(1e-6 * 1000) diff --git a/src/groovylight/test_swapbuffer.py b/src/groovylight/test_swapbuffer.py index bee10fb..2b5cce2 100644 --- a/src/groovylight/test_swapbuffer.py +++ b/src/groovylight/test_swapbuffer.py @@ -5,11 +5,11 @@ from amaranth.lib.wiring import In, Out from amaranth.lib.memory import Memory, WritePort from amaranth.sim import Simulator -from .bitslicer import Hub75StringDriver, Rgb888Layout, SwapBuffer +from .bitslicer import Hub75StringDriver, Rgb666Layout, SwapBuffer def test_swapbuffer(): - dut = SwapBuffer(Rgb888Layout, 512) + dut = SwapBuffer(Rgb666Layout, 512) sim = Simulator(dut) sim.add_clock(1e-6)