diff --git a/src/groovylight/bitslicer.py b/src/groovylight/bitslicer.py index 0152652..b152f34 100644 --- a/src/groovylight/bitslicer.py +++ b/src/groovylight/bitslicer.py @@ -70,6 +70,7 @@ class Hub75Data(wiring.Signature): } ) + class SwapBuffer(wiring.Component): """A pair of BRAMs for holdling line data that are swapped between using an external signal. @@ -129,7 +130,6 @@ class SwapBuffer(wiring.Component): # 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), @@ -141,63 +141,49 @@ class SwapBuffer(wiring.Component): class Hub75StringDriver(wiring.Component): - """A data driver for Hub75 panels. This accesses the line memory and feeds out the data""" + """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 + it should send. + """ bcm_select: In(3) done: Out(1) start: In(1) - active_bank: In(1) # the bank to use - bram_port: Out(WritePort.Signature( - addr_width=9, shape=Rgb888Layout - )) # the other line to be swapped to. + 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) + def elaborate(self, platform: Platform) -> Module: m = Module() # add two memories - m.submodules.bram0 = bram0 = Memory(shape=Rgb888Layout, depth=512, init=[]) - m.submodules.bram1 = bram1 = Memory(shape=Rgb888Layout, depth=512, init=[]) - # We use two brams here so we can swap between each - that way we can update the data - # while displaying the other line. Switching is controlled by the coordinator. - - readports = Mux([bram0.read_port(), bram1.read_port()]) - active_readport = readports[self.active_bank] - m.d.comb += active_readport.eq(readports[self.active_bank]) - writeports = Array([bram0.write_port(), bram1.write_port()]) - m.d.comb += self.bram_port.eq(writeports[~self.active_bank]) - - # We want to set up the bram ports - port0 = bram0.read_port() - port1 = bram1.read_port() - # the enable for these ports is based on active bank. - m.d.comb += port0.en.eq(self.active_bank) - m.d.comb += port1.en.eq(~self.active_bank) - - counter = Signal(32) + self._counter = counter = Signal(32) m.d.sync += counter.eq(counter + 1) ram_rgb_slice = Cat( - active_readport.data["red"].bit_select(self.bcm_shift, 1), - active_readport.data["blue"].bit_select(self.bcm_shift, 1), - active_readport.data["green"].bit_select(self.bcm_shift, 1), + 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), ) - pixnum = Signal(8, reset=127) - pixrow = Signal(1, reset=0) - m.d.comb += self.buf_addr.eq(Cat(pixrow, pixnum)) + 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)) with m.FSM(): with m.State("init"): m.d.sync += [ self.done.eq(0), counter.eq(0), - pixnum.eq(127), + pixnum.eq(self.panel_length - 1), pixrow.eq(0), ] with m.If(self.start == 1): - m.d.sync += active_readport.en.eq(1) + m.d.sync += self.bram_port.en.eq(1) m.next = "prefetch" with m.State("prefetch"): with m.If(counter == 0): @@ -209,23 +195,32 @@ class Hub75StringDriver(wiring.Component): 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: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): + + m.next = "done" + with m.State("done"): + m.d.sync += self.done.eq(1) + m.next = "init" return m -def test_stringdriver(): - dut = Hub75StringDriver() - sim = Simulator(dut) - sim.add_clock(1e-6) - - async def testbench(ctx): - pass - - sim.add_testbench(testbench) - with sim.write_vcd("output.vcd"): - sim.run_until(1e-6 * 1000) - - class Hub75Coordinator(wiring.Component): """A shared-control hub75 driver""" diff --git a/src/groovylight/test_hub75.py b/src/groovylight/test_hub75.py new file mode 100644 index 0000000..bbf8bb6 --- /dev/null +++ b/src/groovylight/test_hub75.py @@ -0,0 +1,37 @@ +from amaranth import Array, 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, WritePort +from amaranth.sim import Simulator + +from .bitslicer import Hub75StringDriver, Rgb888Layout + + +def test_stringdriver(): + # 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=[]) + port = mem.read_port() + + wiring.connect(m, port, dut.bram_port) + + 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) diff --git a/src/groovylight/test_swapbank.py b/src/groovylight/test_swapbuffer.py similarity index 95% rename from src/groovylight/test_swapbank.py rename to src/groovylight/test_swapbuffer.py index 555b480..bee10fb 100644 --- a/src/groovylight/test_swapbank.py +++ b/src/groovylight/test_swapbuffer.py @@ -27,7 +27,8 @@ def test_swapbuffer(): ctx.set(dut.selector, 1) await ctx.tick().repeat(2) # takes two clocks after switching selector to output data. assert ctx.get(dut.read_port.data) == test_color - + + # TODO: add more assertions/verification sim.add_testbench(testbench) with sim.write_vcd("output.vcd"): sim.run_until(1e-6 * 1000)