generated from saji/ecp5-template
refactor around rgb666
remove 4xclocking for double-fetch architecture (still using 2xclock for better S/H)
This commit is contained in:
parent
fe4a902bd8
commit
66b492e147
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue