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,20 +43,21 @@ 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 pass
elif self.geom.rotation == DisplayRotation.UPDOWN:
# r 90, +y
pass
with m.State("done"): with m.State("done"):
m.d.comb += self.done.eq(1) m.d.comb += self.done.eq(1)
m.next = "init" m.next = "init"

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."""
@ -212,7 +185,7 @@ class DisplayGeometry:
return sum return sum
def add_string(self, s: DisplayString): def add_string(self, s: DisplayString):
"""Add a new string to the display. """Add a new string to the display.
When in strict mode, this method will throw an exception if this new string When in strict mode, this method will throw an exception if this new string
will overlap with an existing string. will overlap with an existing string.
""" """

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