Compare commits

..

No commits in common. "2893262f87dbfc7d6af9bdf308cd4ad0f529b437" and "6b034b0176526edca584f40c1b58c014d6559c98" have entirely different histories.

3 changed files with 23 additions and 93 deletions

View file

@ -22,31 +22,6 @@ 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."""
@ -54,8 +29,7 @@ class AddressGenerator(wiring.Component):
self.geom = geom self.geom = geom
super().__init__( super().__init__(
{ {
"coordstream": Out( stream.Signature(CoordLayout.array(geom.dimensions.mux)) "coordstream": Out(stream.Signature(CoordLayout)),
),
"start": In(1), "start": In(1),
"done": Out(1), "done": Out(1),
"addr": In(geom.dimensions.addr_bits), "addr": In(geom.dimensions.addr_bits),
@ -69,19 +43,20 @@ 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"):
# stream data out as long as it's valid. if self.geom.rotation == DisplayRotation.LEFTRIGHT:
# 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)

View file

@ -110,17 +110,17 @@ class DisplayRotation(Enum):
Generally, prefer LEFTRIGHT/UPDOWN over other rotations. Generally, prefer LEFTRIGHT/UPDOWN over other rotations.
""" """
R0 = 0 LEFTRIGHT = 0
R90 = 1 UPDOWN = 1
R180 = 2 DOWNUP = 2
R270 = 3 # why are you like this. RIGHTLEFT = 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) screen coordinates of the top-left of the display. position: (X,Y) coordinates of the local top-left of the display
dimensions: (length, height) local-coordinate dimensions of the display. dimensions: (length, height) local-coordinate dimensions of the display.
@ -129,7 +129,8 @@ class DisplayString:
position: Coord position: Coord
dimensions: DisplayDimensions dimensions: DisplayDimensions
rotation: DisplayRotation = DisplayRotation.R0 rotation: DisplayRotation
# TODO: encode muxing
@property @property
def bbox(self) -> BBox: def bbox(self) -> BBox:
@ -139,10 +140,14 @@ 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.R0 | DisplayRotation.R270: case DisplayRotation.LEFTRIGHT:
return BBox(Coord(x, y), Coord(x + l, y + h)) return BBox(Coord(x, y), Coord(x + l, y + h))
case DisplayRotation.R90 | DisplayRotation.R180: case DisplayRotation.UPDOWN:
return BBox(Coord(x, y), Coord(x + h, y + l)) return BBox(Coord(x - h, y), Coord(x, 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."""
@ -152,38 +157,6 @@ 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."""

View file

@ -1,4 +1,4 @@
from ..geom import Coord, BBox, DisplayString, DisplayDimensions, DisplayRotation from ..geom import Coord, BBox
import pytest import pytest
@ -37,21 +37,3 @@ 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