diff --git a/src/groovylight/bitslicer.py b/src/groovylight/bitslicer.py index 23b56eb..a1a3e13 100644 --- a/src/groovylight/bitslicer.py +++ b/src/groovylight/bitslicer.py @@ -1,7 +1,8 @@ -from amaranth import Module, Cat, Signal, Assert, unsigned +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 class RGBLayout(data.StructLayout): @@ -55,9 +56,10 @@ class Hub75Ctrl(wiring.Signature): class Hub75Data(wiring.Signature): - """ Data lines for HUB75 displays. When combined with Hub75Ctrl, this forms a complete HUB75 interface. + """Data lines for HUB75 displays. When combined with Hub75Ctrl, this forms a complete HUB75 interface. These are kept separate as some devices have a single set of control signals. """ + def __init__(self): super().__init__( { @@ -65,31 +67,91 @@ class Hub75Data(wiring.Signature): "rgb1": Out(rgb111_hub75), } ) + + + +def get_lineram() -> Memory: + return Memory(shape=rgb888_layout, depth=512, init=[]) + + + 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""" + bcm_select: In(3) - - -class Hub75Coordinator(wiring.Component): - """ A shared-control hub75 driver""" - pass - -class Bitslicer(wiring.Component): - start_write: In(1) done: Out(1) - bitplane_addr: Out(11) - bitplane_wren: Out(1) - bitplane_data: Out(6) + start: In(1) + active_bank: In(1) # the bank to use + bram_port: WritePort.Signature(addr_width=9, shape=rgb888_layout) + out: Out(Hub75Data()) + def elaborate(self, platform: Platform) -> Module: m = Module() - bitplane_bit = Signal(3) + + # add two memories + m.submodules.bram0 = bram0 = Memory(shape=rgb888_layout, depth=512, init=[]) + m.submodules.bram1 = bram1 = Memory(shape=rgb888_layout, 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 = Array([bram0.read_port(), bram1.read_port()]) + active_readport = readports[self.active_bank] + m.d.comb += self.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]) + + 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), + ) + + pixnum = Signal(8, reset=127) + pixrow = Signal(1, reset=0) + m.d.comb += self.buf_addr.eq(Cat(pixrow, pixnum)) + with m.FSM(): with m.State("init"): - m.d.sync += bitplane_bit.eq(0) - m.d.sync += self.done.eq(0) + m.d.sync += [ + self.done.eq(0), + counter.eq(0), + pixnum.eq(127), + pixrow.eq(0), + ] + with m.If(self.start == 1): + m.d.sync += active_readport.en.eq(1) + m.next = "prefetch" + 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.out.rgb0.eq(ram_rgb_slice) + with m.Elif(counter == 3): + m.d.sync += [self.out.rgb1.eq(ram_rgb_slice), counter.eq(0)] + m.next = "writerow" + + + return m + + +class Hub75Coordinator(wiring.Component): + """A shared-control hub75 driver""" + + def __init__(self, n_strings=1): + self.n_strings = n_strings + super().__init__() + + def elaborate(self, platform: Platform) -> Module: + m = Module() return m @@ -208,3 +270,8 @@ class Hub75EDriver(wiring.Component): m.next = "init" return m + +if __name__ == "__main__": + m = Hub75EDriver() + from amaranth.cli import main + main(m) diff --git a/src/groovylight/sdram.py b/src/groovylight/sdram.py new file mode 100644 index 0000000..e00a4ce --- /dev/null +++ b/src/groovylight/sdram.py @@ -0,0 +1,70 @@ +# SDRAM controller for the Colorlight 5a-75b + + +from amaranth import Module, Cat, Signal, Assert, unsigned +from amaranth.build import Platform +from amaranth.lib import wiring, data, enum +from amaranth.lib.wiring import In, Out + + +# RAM Specs: +# 4 banks, 2048 rows, 256 columns. +# 11 row bits, 8 column bits. +# word size = 32 +# 8 megabytes data. + + +sdram_control_layout = data.StructLayout( + { + "nCS": 1, + "nRAS": 1, + "nCAS": 1, + "nWE": 1, + } +) + + + +class _WriteBurstLength(enum.Enum, shape=1): + """MRS field""" + BURST = 0 + SINGLE_BIT = 1 + +class _TestMode(enum.Enum, shape=2): + """ The "test mode" of the sdram. This is always zero pretty much""" + MODE_REGISTER_SET = 0 + RESERVED0 = 1 + RESERVED1 = 2 + RESERVED2 = 3 + +class _CASLatency(enum.Enum, shape=3): + """ How many cycles of latency for the column address select to complete """ + CYCL2 = 2 + CYCL3 = 3 + +class _BurstType(enum.Enum, shape=1): + SEQUENTIAL = 0 + INTERLEAVED = 1 + +class _BurstLength(enum.IntEnum, shape=3): + """ The size of the burst """ + SINGLE = 0 + DUAL = 1 + QUAD = 2 + OCT = 3 + FULL_PAGE = 7 + + +class _Command(enum.Enum): + """ Command set for SDRAM """ + MRS_WRITE = 0 + ACTIVATE = 1 + PRECHARGE = 2 + WRITE = 3 + READ = 4 + CBR = 5 # auto refresh + SELF_REFRESH = 6 + BRST_STOP = 7 + NOP = 8 + +