Skip to content

Commit b45588d

Browse files
committed
applet.video.ws2812_output: port to V2
1 parent 2e27625 commit b45588d

File tree

2 files changed

+90
-58
lines changed

2 files changed

+90
-58
lines changed

software/glasgow/applet/video/ws2812_output/__init__.py

Lines changed: 88 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import logging
22
import asyncio
3+
34
from amaranth import *
4-
from amaranth.lib import io
5+
from amaranth.lib import io, wiring, stream
6+
from amaranth.lib.wiring import In
57

6-
from ....support.endpoint import *
7-
from ....gateware.pll import *
8-
from ... import *
8+
from glasgow.abstract import AbstractAssembly, GlasgowPin, PortGroup
9+
from glasgow.applet import GlasgowAppletV2
10+
from glasgow.support.endpoint import *
11+
from glasgow.gateware.pll import *
912

1013

1114
class VideoWS2812Output(Elaboratable):
12-
def __init__(self, ports):
15+
def __init__(self, ports: PortGroup):
1316
self.ports = ports
1417
self.out = Signal(len(ports.out))
1518

@@ -22,14 +25,19 @@ def elaborate(self, platform):
2225
return m
2326

2427

25-
class VideoWS2812OutputSubtarget(Elaboratable):
26-
def __init__(self, ports, count, pix_in_size, pix_out_size, pix_format_func, out_fifo):
27-
self.ports = ports
28-
self.count = count
29-
self.pix_in_size = pix_in_size
30-
self.pix_out_size = pix_out_size
28+
class VideoWS2812OutputComponent(wiring.Component):
29+
i_stream: In(stream.Signature(8))
30+
31+
def __init__(
32+
self, ports: PortGroup, count: int, pix_in_size: int, pix_out_size: int, pix_format_func
33+
):
34+
self.ports = ports
35+
self.count = count
36+
self.pix_in_size = pix_in_size
37+
self.pix_out_size = pix_out_size
3138
self.pix_format_func = pix_format_func
32-
self.out_fifo = out_fifo
39+
40+
super().__init__()
3341

3442
def elaborate(self, platform):
3543
# Safe timings:
@@ -54,29 +62,29 @@ def elaborate(self, platform):
5462
pix_out_size = self.pix_out_size
5563
pix_out_bpp = pix_out_size * 8
5664

57-
cyc_ctr = Signal(range(t_reset+1))
58-
bit_ctr = Signal(range(pix_out_bpp+1))
59-
byt_ctr = Signal(range((pix_in_size)+1))
60-
pix_ctr = Signal(range(self.count+1))
65+
cyc_ctr = Signal(range(t_reset + 1))
66+
bit_ctr = Signal(range(pix_out_bpp + 1))
67+
byt_ctr = Signal(range((pix_in_size) + 1))
68+
pix_ctr = Signal(range(self.count + 1))
6169
word_ctr = Signal(range(max(2, len(self.ports.out))))
6270

63-
pix = Array([ Signal(8) for i in range((pix_in_size) - 1) ])
71+
pix = Array([Signal(8) for i in range((pix_in_size) - 1)])
6472
word = Signal(pix_out_bpp * len(self.ports.out))
6573

6674
with m.FSM():
6775
with m.State("LOAD"):
6876
m.d.comb += [
69-
self.out_fifo.r_en.eq(1),
77+
self.i_stream.ready.eq(1),
7078
output.out.eq(0),
7179
]
72-
with m.If(self.out_fifo.r_rdy):
80+
with m.If(self.i_stream.valid):
7381
with m.If(byt_ctr < ((pix_in_size) - 1)):
7482
m.d.sync += [
75-
pix[byt_ctr].eq(self.out_fifo.r_data),
83+
pix[byt_ctr].eq(self.i_stream.payload),
7684
byt_ctr.eq(byt_ctr + 1),
7785
]
7886
with m.Else():
79-
p = self.pix_format_func(*pix, self.out_fifo.r_data)
87+
p = self.pix_format_func(*pix, self.i_stream.payload)
8088
m.d.sync += word.eq(Cat(word[pix_out_bpp:], p))
8189
with m.If(word_ctr < (len(self.ports.out) - 1)):
8290
m.d.sync += [
@@ -91,8 +99,10 @@ def elaborate(self, platform):
9199
m.d.comb += output.out.eq((1 << len(self.ports.out)) - 1)
92100
m.d.sync += cyc_ctr.eq(cyc_ctr + 1)
93101
with m.Elif(cyc_ctr < t_one):
94-
m.d.comb += (o.eq(word[(pix_out_bpp - 1) + (pix_out_bpp * i)])
95-
for i,o in enumerate(output.out))
102+
m.d.comb += (
103+
o.eq(word[(pix_out_bpp - 1) + (pix_out_bpp * i)])
104+
for i, o in enumerate(output.out)
105+
)
96106
m.d.sync += cyc_ctr.eq(cyc_ctr + 1)
97107
with m.Elif(cyc_ctr < t_period):
98108
m.d.comb += output.out.eq(0)
@@ -135,7 +145,34 @@ def elaborate(self, platform):
135145
return m
136146

137147

138-
class VideoWS2812OutputApplet(GlasgowApplet):
148+
class VideoWS2812OutputInterface:
149+
def __init__(
150+
self,
151+
logger: logging.Logger,
152+
assembly: AbstractAssembly,
153+
*,
154+
out: tuple[GlasgowPin],
155+
count: int,
156+
pix_in_size: int,
157+
pix_out_size: int,
158+
pix_format_func,
159+
buffer: int,
160+
):
161+
self._logger = logger
162+
ports = assembly.add_port_group(out=out)
163+
component = assembly.add_submodule(
164+
VideoWS2812OutputComponent(ports, count, pix_in_size, pix_out_size, pix_format_func)
165+
)
166+
self._pipe = assembly.add_out_pipe(
167+
component.i_stream, buffer_size=len(out) * count * pix_in_size * buffer
168+
)
169+
170+
async def write(self, data):
171+
await self._pipe.send(data)
172+
await self._pipe.flush()
173+
174+
175+
class VideoWS2812OutputApplet(GlasgowAppletV2):
139176
logger = logging.getLogger(__name__)
140177
help = "display video via WS2812 LEDs"
141178
description = """
@@ -152,66 +189,61 @@ class VideoWS2812OutputApplet(GlasgowApplet):
152189

153190
@classmethod
154191
def add_build_arguments(cls, parser, access):
155-
super().add_build_arguments(parser, access)
156-
192+
access.add_voltage_argument(parser)
157193
access.add_pins_argument(parser, "out", width=range(1, 17), required=True)
158194
parser.add_argument(
159195
"-c", "--count", metavar="N", type=int, required=True,
160196
help="set the number of LEDs per string")
161197
parser.add_argument(
162198
"-f", "--pix-fmt", metavar="F", choices=cls.pixel_formats.keys(), default="RGB-BRG",
163199
help="set the pixel format (one of: %(choices)s, default: %(default)s)")
164-
165-
def build(self, target, args):
166-
self.pix_in_size, pix_out_size, pix_format_func = self.pixel_formats[args.pix_fmt]
167-
168-
self.mux_interface = iface = target.multiplexer.claim_interface(self, args)
169-
subtarget = iface.add_subtarget(VideoWS2812OutputSubtarget(
170-
ports=iface.get_port_group(out=args.out),
171-
count=args.count,
172-
pix_in_size=self.pix_in_size,
173-
pix_out_size=pix_out_size,
174-
pix_format_func=pix_format_func,
175-
out_fifo=iface.get_out_fifo(),
176-
))
177-
178-
return subtarget
179-
180-
@classmethod
181-
def add_run_arguments(cls, parser, access):
182-
super().add_run_arguments(parser, access)
183-
184200
parser.add_argument(
185201
"-b", "--buffer", metavar="N", type=int, default=16,
186202
help="set the number of frames to buffer internally (buffered twice)")
187203

188-
async def run(self, device, args):
189-
buffer_size = len(args.out) * args.count * self.pix_in_size * args.buffer
190-
return await device.demultiplexer.claim_interface(self, self.mux_interface, args,
191-
write_buffer_size=buffer_size)
204+
def build(self, args):
205+
self.pix_in_size, pix_out_size, pix_format_func = self.pixel_formats[args.pix_fmt]
206+
207+
with self.assembly.add_applet(self):
208+
self.assembly.use_voltage(args.voltage)
209+
self.ws2812_iface = VideoWS2812OutputInterface(
210+
self.logger,
211+
self.assembly,
212+
out=args.out,
213+
count=args.count,
214+
pix_in_size=self.pix_in_size,
215+
pix_out_size=pix_out_size,
216+
pix_format_func=pix_format_func,
217+
buffer=args.buffer,
218+
)
192219

193220
@classmethod
194-
def add_interact_arguments(cls, parser):
221+
def add_run_arguments(cls, parser):
195222
ServerEndpoint.add_argument(parser, "endpoint")
196223

197-
async def interact(self, device, args, leds):
224+
async def run(self, args):
198225
frame_size = len(args.out) * args.count * self.pix_in_size
199226
buffer_size = frame_size * args.buffer
200-
endpoint = await ServerEndpoint("socket", self.logger, args.endpoint,
201-
queue_size=buffer_size, deprecated_cancel_on_eof=True)
227+
endpoint = await ServerEndpoint(
228+
"socket",
229+
self.logger,
230+
args.endpoint,
231+
queue_size=buffer_size,
232+
deprecated_cancel_on_eof=True,
233+
)
202234
while True:
203235
try:
204236
data = await asyncio.shield(endpoint.recv(buffer_size))
205237
partial = len(data) % frame_size
206238
while partial:
207239
data += await asyncio.shield(endpoint.recv(frame_size - partial))
208240
partial = len(data) % frame_size
209-
await leds.write(data)
210-
await leds.flush(wait=False)
241+
await self.ws2812_iface.write(data)
211242
except asyncio.CancelledError:
212243
pass
213244

214245
@classmethod
215246
def tests(cls):
216247
from . import test
248+
217249
return test.VideoWS2812OutputAppletTestCase
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from ... import *
1+
from glasgow.applet import GlasgowAppletV2TestCase, synthesis_test
22
from . import VideoWS2812OutputApplet
33

44

5-
class VideoWS2812OutputAppletTestCase(GlasgowAppletTestCase, applet=VideoWS2812OutputApplet):
5+
class VideoWS2812OutputAppletTestCase(GlasgowAppletV2TestCase, applet=VideoWS2812OutputApplet):
66
@synthesis_test
77
def test_build(self):
88
self.assertBuilds(args=["--out", "A0:3", "-c", "1024"])

0 commit comments

Comments
 (0)