generated from saji/ecp5-template
Compare commits
2 commits
6b034b0176
...
2893262f87
Author | SHA1 | Date | |
---|---|---|---|
saji | 2893262f87 | ||
saji | 261dedc4dc |
|
@ -22,6 +22,31 @@ logger = logging.getLogger(__name__)
|
||||||
CoordLayout = data.StructLayout({"x": unsigned(32), "y": unsigned(32)})
|
CoordLayout = data.StructLayout({"x": unsigned(32), "y": unsigned(32)})
|
||||||
|
|
||||||
|
|
||||||
|
class AddressConverter(wiring.Component):
|
||||||
|
"""Translates display (x,y) into full screen (x,y) based on geometry"""
|
||||||
|
|
||||||
|
def __init__(self, geom: DisplayString, *, src_loc_at=0):
|
||||||
|
self.geom = geom
|
||||||
|
super().__init__(
|
||||||
|
{
|
||||||
|
"input_x": In(geom.dimensions.length),
|
||||||
|
"addr": In(unsigned(geom.dimensions.addr_bits)),
|
||||||
|
"output": Out(CoordLayout).array(geom.dimensions.mux),
|
||||||
|
},
|
||||||
|
src_loc_at=src_loc_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
def elaborate(self, platform: Platform) -> Module:
|
||||||
|
m = Module()
|
||||||
|
|
||||||
|
for mux in range(self.geom.dimensions.mux):
|
||||||
|
m.d.comb += self.output[mux].eq(
|
||||||
|
self.geom.translate_coord(self.input_x, self.addr, mux)
|
||||||
|
)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
class AddressGenerator(wiring.Component):
|
class AddressGenerator(wiring.Component):
|
||||||
"""Generates (x,y) sequences corresponding to a display row."""
|
"""Generates (x,y) sequences corresponding to a display row."""
|
||||||
|
|
||||||
|
@ -29,7 +54,8 @@ class AddressGenerator(wiring.Component):
|
||||||
self.geom = geom
|
self.geom = geom
|
||||||
super().__init__(
|
super().__init__(
|
||||||
{
|
{
|
||||||
"coordstream": Out(stream.Signature(CoordLayout)),
|
"coordstream": Out( stream.Signature(CoordLayout.array(geom.dimensions.mux))
|
||||||
|
),
|
||||||
"start": In(1),
|
"start": In(1),
|
||||||
"done": Out(1),
|
"done": Out(1),
|
||||||
"addr": In(geom.dimensions.addr_bits),
|
"addr": In(geom.dimensions.addr_bits),
|
||||||
|
@ -43,20 +69,19 @@ class AddressGenerator(wiring.Component):
|
||||||
counter = Signal(self.geom.dimensions.length)
|
counter = Signal(self.geom.dimensions.length)
|
||||||
|
|
||||||
# based on the geometry we generate x,y pairs.
|
# based on the geometry we generate x,y pairs.
|
||||||
|
m.submodules.translate = translate = AddressConverter(self.geom)
|
||||||
|
|
||||||
with m.FSM():
|
with m.FSM():
|
||||||
with m.State("init"):
|
with m.State("init"):
|
||||||
m.d.comb += self.done.eq(0)
|
m.d.comb += self.done.eq(0)
|
||||||
m.d.sync += counter.eq(0)
|
m.d.sync += counter.eq(0)
|
||||||
|
m.d.comb += self.coordstream.valid.eq(0)
|
||||||
with m.If(self.start):
|
with m.If(self.start):
|
||||||
m.next = "run"
|
m.next = "run"
|
||||||
|
|
||||||
with m.State("run"):
|
with m.State("run"):
|
||||||
if self.geom.rotation == DisplayRotation.LEFTRIGHT:
|
# stream data out as long as it's valid.
|
||||||
# default case, +x.
|
|
||||||
pass
|
|
||||||
elif self.geom.rotation == DisplayRotation.UPDOWN:
|
|
||||||
# r 90, +y
|
|
||||||
pass
|
pass
|
||||||
with m.State("done"):
|
with m.State("done"):
|
||||||
m.d.comb += self.done.eq(1)
|
m.d.comb += self.done.eq(1)
|
||||||
|
|
|
@ -110,17 +110,17 @@ class DisplayRotation(Enum):
|
||||||
Generally, prefer LEFTRIGHT/UPDOWN over other rotations.
|
Generally, prefer LEFTRIGHT/UPDOWN over other rotations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LEFTRIGHT = 0
|
R0 = 0
|
||||||
UPDOWN = 1
|
R90 = 1
|
||||||
DOWNUP = 2
|
R180 = 2
|
||||||
RIGHTLEFT = 3 # why are you like this.
|
R270 = 3 # why are you like this.
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class DisplayString:
|
class DisplayString:
|
||||||
"""Internal class to represent a string of HUB75 displays.
|
"""Internal class to represent a string of HUB75 displays.
|
||||||
|
|
||||||
position: (X,Y) coordinates of the local top-left of the display
|
position: (X,Y) screen coordinates of the top-left of the display.
|
||||||
|
|
||||||
dimensions: (length, height) local-coordinate dimensions of the display.
|
dimensions: (length, height) local-coordinate dimensions of the display.
|
||||||
|
|
||||||
|
@ -129,8 +129,7 @@ class DisplayString:
|
||||||
|
|
||||||
position: Coord
|
position: Coord
|
||||||
dimensions: DisplayDimensions
|
dimensions: DisplayDimensions
|
||||||
rotation: DisplayRotation
|
rotation: DisplayRotation = DisplayRotation.R0
|
||||||
# TODO: encode muxing
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bbox(self) -> BBox:
|
def bbox(self) -> BBox:
|
||||||
|
@ -140,14 +139,10 @@ class DisplayString:
|
||||||
l = self.dimensions.length
|
l = self.dimensions.length
|
||||||
h = self.dimensions.height
|
h = self.dimensions.height
|
||||||
match self.rotation:
|
match self.rotation:
|
||||||
case DisplayRotation.LEFTRIGHT:
|
case DisplayRotation.R0 | DisplayRotation.R270:
|
||||||
return BBox(Coord(x, y), Coord(x + l, y + h))
|
return BBox(Coord(x, y), Coord(x + l, y + h))
|
||||||
case DisplayRotation.UPDOWN:
|
case DisplayRotation.R90 | DisplayRotation.R180:
|
||||||
return BBox(Coord(x - h, y), Coord(x, y + l))
|
return BBox(Coord(x, y), Coord(x + h, y + l))
|
||||||
case DisplayRotation.DOWNUP:
|
|
||||||
return BBox(Coord(x, y + l), Coord(x + h, y))
|
|
||||||
case DisplayRotation.RIGHTLEFT:
|
|
||||||
return BBox(Coord(x - l, y - h), Coord(x, y))
|
|
||||||
|
|
||||||
def contains_pix(self, coord: Coord) -> bool:
|
def contains_pix(self, coord: Coord) -> bool:
|
||||||
"""Checks if the given coordinate is inside this display."""
|
"""Checks if the given coordinate is inside this display."""
|
||||||
|
@ -157,6 +152,38 @@ class DisplayString:
|
||||||
"""Checks if the given BBox intersects with this display"""
|
"""Checks if the given BBox intersects with this display"""
|
||||||
return self.bbox.intersects(box)
|
return self.bbox.intersects(box)
|
||||||
|
|
||||||
|
def translate_coord(self, pixnum, addr, mux):
|
||||||
|
"""Helper function to translate string coordinates to screen coordinates"""
|
||||||
|
|
||||||
|
assert mux < self.dimensions.mux, "Mux must be within the mux of the display"
|
||||||
|
|
||||||
|
x = self.position.x
|
||||||
|
y = self.position.y
|
||||||
|
|
||||||
|
addrshift = addr + (self.dimensions.height // self.dimensions.mux) * mux
|
||||||
|
#
|
||||||
|
match self.rotation:
|
||||||
|
case DisplayRotation.R0:
|
||||||
|
# x is linear
|
||||||
|
return {
|
||||||
|
"x": x + pixnum,
|
||||||
|
"y": y + addrshift,
|
||||||
|
}
|
||||||
|
case DisplayRotation.R90:
|
||||||
|
# x is height - addrshift
|
||||||
|
return {
|
||||||
|
"x": x + self.dimensions.height - addrshift - 1,
|
||||||
|
"y": y + pixnum,
|
||||||
|
}
|
||||||
|
case DisplayRotation.R180:
|
||||||
|
pass
|
||||||
|
case DisplayRotation.R270:
|
||||||
|
# x and y are both length - pixnum
|
||||||
|
return {
|
||||||
|
"x": x + self.dimensions.length - 1 - pixnum,
|
||||||
|
"y": y + addrshift,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class DisplayGeometry:
|
class DisplayGeometry:
|
||||||
"""Represents a display based on several strings in different positions."""
|
"""Represents a display based on several strings in different positions."""
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from ..geom import Coord, BBox
|
from ..geom import Coord, BBox, DisplayString, DisplayDimensions, DisplayRotation
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -37,3 +37,21 @@ def test_bbox():
|
||||||
BBox(Coord(0, 0), Coord(1, 0))
|
BBox(Coord(0, 0), Coord(1, 0))
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
BBox(Coord(1, 1), Coord(0, 0))
|
BBox(Coord(1, 1), Coord(0, 0))
|
||||||
|
|
||||||
|
|
||||||
|
ds_testdata = [
|
||||||
|
(DisplayRotation.R0, (0, 0, 0), {"x": 3, "y": 0}),
|
||||||
|
(DisplayRotation.R0, (40, 2, 0), {"x": 43, "y": 2}),
|
||||||
|
(DisplayRotation.R0, (40, 2, 1), {"x": 43, "y": 34}),
|
||||||
|
(DisplayRotation.R90, (40, 0, 0), {"x": 66, "y": 40}),
|
||||||
|
(DisplayRotation.R90, (120, 2, 0), {"x": 64, "y": 120}),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("rot, inp, expected", ds_testdata)
|
||||||
|
def test_displaystring(rot, inp, expected):
|
||||||
|
ds = DisplayString(Coord(3, 0), DisplayDimensions(128, 64), rot)
|
||||||
|
|
||||||
|
res = ds.translate_coord(*inp)
|
||||||
|
|
||||||
|
assert res == expected
|
||||||
|
|
Loading…
Reference in a new issue