refactor around rgb666

remove 4xclocking for double-fetch architecture (still using 2xclock for
better S/H)
This commit is contained in:
saji 2024-09-19 00:03:19 -05:00
parent fe4a902bd8
commit 66b492e147
4 changed files with 76 additions and 86 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)