Skip to content

Commit 4f66df4

Browse files
committed
adiuvo_forgix: add rich demo analyzer support
Add the richer CSR-driven RGB LED demo controls with PWM, pattern selection, trigger/status counters and optional edge connector outputs. Add an optional LiteScope analyzer for the demo and SPIBone traffic, with narrowed probe widths so the 512-sample rich demo fits in the T8F49 memory resources.
1 parent 84220a2 commit 4f66df4

1 file changed

Lines changed: 173 additions & 27 deletions

File tree

litex_boards/targets/adiuvo_forgix.py

Lines changed: 173 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
# Copyright (c) 2026 Florent Kermarrec <florent@enjoy-digital.fr>
77
# SPDX-License-Identifier: BSD-2-Clause
88

9+
import os
10+
911
from migen import *
1012

1113
from litex.gen import *
@@ -39,41 +41,97 @@ def __init__(self, platform, sys_clk_freq):
3941
# DemoLEDs -----------------------------------------------------------------------------------------
4042

4143
class DemoLEDs(LiteXModule):
42-
def __init__(self, pads, sys_clk_freq, polarity=1):
44+
def __init__(self, pads, sys_clk_freq, polarity=1, gpio_pads=None):
4345
n = len(pads)
44-
self._mode = CSRStorage(2, reset=1, description="LED demo mode: 0 fixed, 1 breathe, 2 chase, 3 rainbow.")
46+
self._mode = CSRStorage(3, reset=1, description="LED demo mode: 0 direct PWM, 1 breathe, 2 chase, 3 rainbow, 4 strobe, 5 sparkle.")
4547
self._rgb = CSRStorage(n, reset=2**n - 1, description="RGB LED mask/control value.")
4648
self._speed = CSRStorage(8, reset=8, description="Animation speed; larger values slow the demo down.")
49+
self._pwm_r = CSRStorage(8, reset=0xff, description="Red LED PWM duty cycle.")
50+
self._pwm_g = CSRStorage(8, reset=0xff, description="Green LED PWM duty cycle.")
51+
self._pwm_b = CSRStorage(8, reset=0xff, description="Blue LED PWM duty cycle.")
52+
self._pwm_period = CSRStorage(8, reset=0xff, description="PWM period/top value.")
53+
self._pattern = CSRStorage(4, reset=0, description="Pattern variant selector.")
54+
self._trigger = CSRStorage(1, reset=0, description="Write to inject a manual demo event.")
55+
self._gpio_mask = CSRStorage(18, reset=0, description="Edge connector output override mask.")
56+
self._gpio_value = CSRStorage(18, reset=0, description="Edge connector output override value.")
4757
self._counter = CSRStatus(32, description="Free-running demo counter.")
58+
self._frame_counter = CSRStatus(32, description="Animation frame counter.")
59+
self._event_counter = CSRStatus(32, description="Frame/manual event counter.")
60+
self._status = CSRStatus(8, description="Packed demo status.")
4861

4962
# # #
5063

51-
counter = Signal(32)
52-
prescaler = Signal(21)
53-
step = Signal(8)
54-
pwm_counter = Signal(8)
55-
period = Signal(21)
56-
breath = Signal(8)
57-
leds = Signal(n)
64+
self.counter = counter = Signal(32, name="demo_counter")
65+
self.frame_counter = frame_counter = Signal(32, name="demo_frame_counter")
66+
self.event_counter = event_counter = Signal(32, name="demo_event_counter")
67+
self.prescaler = prescaler = Signal(21, name="demo_prescaler")
68+
self.step = step = Signal(8, name="demo_step")
69+
self.mode = mode = Signal(3, name="demo_mode")
70+
self.rgb = rgb = Signal(n, name="demo_rgb")
71+
self.pattern = pattern = Signal(4, name="demo_pattern")
72+
self.pwm_counter = pwm_counter = Signal(8, name="demo_pwm_counter")
73+
self.frame_tick = frame_tick = Signal(name="demo_frame_tick")
74+
self.manual_event = manual_event = Signal(name="demo_manual_event")
75+
self.leds = leds = Signal(n, name="demo_leds")
76+
self.gpio_out = gpio_out = Signal(18, name="demo_gpio_out")
77+
self.gpio_sample = gpio_sample = Signal(8, name="demo_gpio_out_lsb")
78+
79+
period = Signal(21)
80+
breath = Signal(8)
81+
direct = Signal(n, name="demo_direct_leds")
82+
breathe = Signal(n, name="demo_breathe_leds")
83+
chase = Signal(n, name="demo_chase_leds")
84+
rainbow = Signal(n, name="demo_rainbow_leds")
85+
strobe = Signal(n, name="demo_strobe_leds")
86+
sparkle = Signal(n, name="demo_sparkle_leds")
87+
edge_pattern = Signal(18)
88+
chase_index = Signal(2)
89+
color_index = Signal(3)
90+
sparkle_index = Signal(3)
5891

5992
chase_patterns = Array([1, 2, 4, 2])
6093
color_patterns = Array([1, 2, 4, 3, 6, 5, 7, 0])
61-
chase = Signal(n)
6294
color = Signal(n)
95+
sparkle_patterns = Array([1, 4, 2, 5, 3, 6, 7, 0])
6396

6497
self.comb += [
6598
self._counter.status.eq(counter),
99+
self._frame_counter.status.eq(frame_counter),
100+
self._event_counter.status.eq(event_counter),
101+
self._status.status.eq(Cat(self._mode.storage, frame_tick, manual_event, leds)),
102+
mode.eq(self._mode.storage),
103+
rgb.eq(self._rgb.storage),
104+
pattern.eq(self._pattern.storage),
66105
period.eq((self._speed.storage + 1) << 12),
67-
chase.eq(chase_patterns[step[6:8]]),
68-
color.eq(color_patterns[step[5:8]]),
106+
chase_index.eq(step[6:8] + pattern[0:2]),
107+
color_index.eq(step[5:8] + pattern[0:3]),
108+
sparkle_index.eq(counter[16:19] + pattern[0:3]),
109+
chase.eq(chase_patterns[chase_index] & self._rgb.storage),
110+
color.eq(color_patterns[color_index]),
111+
rainbow.eq(color & self._rgb.storage),
112+
strobe.eq(Mux(step[7] ^ pattern[0], self._rgb.storage, 0)),
113+
sparkle.eq(sparkle_patterns[sparkle_index] & self._rgb.storage),
69114
]
70115

71116
self.sync += [
72117
counter.eq(counter + 1),
73-
pwm_counter.eq(pwm_counter + 1),
118+
frame_tick.eq(0),
119+
manual_event.eq(0),
120+
If(pwm_counter >= self._pwm_period.storage,
121+
pwm_counter.eq(0)
122+
).Else(
123+
pwm_counter.eq(pwm_counter + 1)
124+
),
125+
If(self._trigger.wr_stb,
126+
manual_event.eq(1),
127+
event_counter.eq(event_counter + 1)
128+
),
74129
If(prescaler >= period,
75130
prescaler.eq(0),
76-
step.eq(step + 1)
131+
step.eq(step + 1),
132+
frame_tick.eq(1),
133+
frame_counter.eq(frame_counter + 1),
134+
event_counter.eq(event_counter + 1)
77135
).Else(
78136
prescaler.eq(prescaler + 1)
79137
)
@@ -85,22 +143,61 @@ def __init__(self, pads, sys_clk_freq, polarity=1):
85143
).Else(
86144
breath.eq(step)
87145
),
146+
direct[0].eq((pwm_counter < self._pwm_r.storage) & self._rgb.storage[0]),
147+
direct[1].eq((pwm_counter < self._pwm_g.storage) & self._rgb.storage[1]),
148+
direct[2].eq((pwm_counter < self._pwm_b.storage) & self._rgb.storage[2]),
149+
breathe.eq(Replicate(pwm_counter < breath, n) & self._rgb.storage),
88150
If(self._mode.storage == 0,
89-
leds.eq(self._rgb.storage)
151+
leds.eq(direct)
90152
).Elif(self._mode.storage == 1,
91-
leds.eq(Mux(pwm_counter < breath, self._rgb.storage, 0))
153+
leds.eq(breathe)
92154
).Elif(self._mode.storage == 2,
93-
leds.eq(chase & self._rgb.storage)
155+
leds.eq(chase)
156+
).Elif(self._mode.storage == 3,
157+
leds.eq(Mux(pwm_counter < breath, rainbow, 0))
158+
).Elif(self._mode.storage == 4,
159+
leds.eq(strobe)
94160
).Else(
95-
leds.eq(Mux(pwm_counter < breath, color, 0))
161+
leds.eq(sparkle)
96162
),
163+
edge_pattern.eq(Cat(
164+
leds,
165+
frame_tick,
166+
manual_event,
167+
pwm_counter[0],
168+
pwm_counter[7],
169+
step,
170+
counter[0:2],
171+
pattern,
172+
)[0:18]),
173+
gpio_out.eq((edge_pattern & ~self._gpio_mask.storage) | (self._gpio_value.storage & self._gpio_mask.storage)),
174+
gpio_sample.eq(gpio_out[:8]),
97175
pads.eq(leds ^ (polarity*(2**n - 1))),
98176
]
177+
if gpio_pads is not None:
178+
self.comb += gpio_pads.eq(gpio_out)
179+
180+
def get_litescope_groups(self):
181+
return {
182+
0: [
183+
self.frame_tick,
184+
self.manual_event,
185+
self.leds,
186+
self.mode,
187+
self.rgb,
188+
self.step,
189+
self.pattern,
190+
self.pwm_counter,
191+
self.gpio_sample,
192+
],
193+
}
99194

100195
# BaseSoC ------------------------------------------------------------------------------------------
101196

102197
class BaseSoC(SoCMini):
103-
def __init__(self, sys_clk_freq=32e6, with_spibone=False, with_led_chaser=True, with_demo_leds=False, **kwargs):
198+
def __init__(self, sys_clk_freq=32e6, with_spibone=False, with_led_chaser=True,
199+
with_demo_leds=False, with_demo_io=False, with_demo_scope=False,
200+
demo_scope_depth=512, demo_analyzer_csv="analyzer.csv", **kwargs):
104201
platform = adiuvo_forgix.Platform()
105202

106203
# CRG --------------------------------------------------------------------------------------
@@ -123,22 +220,63 @@ def __init__(self, sys_clk_freq=32e6, with_spibone=False, with_led_chaser=True,
123220
self.bus.add_master(name="spibone", master=self.spibone.bus)
124221

125222
# Leds -------------------------------------------------------------------------------------
126-
if with_demo_leds:
127-
self.demo_leds = DemoLEDs(platform.request_all("user_led_n"), sys_clk_freq=sys_clk_freq, polarity=1)
223+
if with_demo_leds or with_demo_io or with_demo_scope:
224+
gpio_pads = platform.request("user_io") if with_demo_io else None
225+
self.demo_leds = DemoLEDs(platform.request_all("user_led_n"),
226+
sys_clk_freq = sys_clk_freq,
227+
polarity = 1,
228+
gpio_pads = gpio_pads)
128229
elif with_led_chaser:
129230
self.leds = LedChaser(
130231
pads = platform.request_all("user_led_n"),
131232
sys_clk_freq = sys_clk_freq,
132233
polarity = 1)
133234

235+
# LiteScope Analyzer -----------------------------------------------------------------------
236+
if with_demo_scope:
237+
from litescope import LiteScopeAnalyzer
238+
analyzer_groups = self.demo_leds.get_litescope_groups()
239+
if with_spibone:
240+
spibone_bus_adr_lsb = Signal(14, name="spibone_bus_adr_lsb")
241+
spibone_bus_dat_w_lsb = Signal(16, name="spibone_bus_dat_w_lsb")
242+
spibone_bus_dat_r_lsb = Signal(16, name="spibone_bus_dat_r_lsb")
243+
self.comb += [
244+
spibone_bus_adr_lsb.eq(self.spibone.bus.adr[:14]),
245+
spibone_bus_dat_w_lsb.eq(self.spibone.bus.dat_w[:16]),
246+
spibone_bus_dat_r_lsb.eq(self.spibone.bus.dat_r[:16]),
247+
]
248+
analyzer_groups[1] = [
249+
self.spibone.bus.cyc,
250+
self.spibone.bus.stb,
251+
self.spibone.bus.we,
252+
self.spibone.bus.ack,
253+
spibone_bus_adr_lsb,
254+
spibone_bus_dat_w_lsb,
255+
spibone_bus_dat_r_lsb,
256+
]
257+
self.analyzer = LiteScopeAnalyzer(analyzer_groups,
258+
depth = demo_scope_depth,
259+
clock_domain = "sys",
260+
samplerate = sys_clk_freq,
261+
register = True,
262+
csr_csv = demo_analyzer_csv)
263+
if hasattr(self, "add_csr"):
264+
self.add_csr("analyzer")
265+
else:
266+
self.csr.add("analyzer")
267+
134268
# Build --------------------------------------------------------------------------------------------
135269

136270
def main():
137271
from litex.build.parser import LiteXArgumentParser
138272
parser = LiteXArgumentParser(platform=adiuvo_forgix.Platform, description="LiteX SoC on Adiuvo Forgix.")
139273
parser.add_target_argument("--sys-clk-freq", default=32e6, type=float, help="System clock frequency.")
140-
parser.add_target_argument("--with-spibone", action="store_true", help="Add SPIBone on the RP2350 SPI pins.")
141-
parser.add_target_argument("--with-demo-leds", action="store_true", help="Add the CSR-controlled RGB LED demo core.")
274+
parser.add_target_argument("--with-spibone", action="store_true", help="Add SPIBone on the RP2350 SPI pins.")
275+
parser.add_target_argument("--with-demo-leds", action="store_true", help="Add the CSR-controlled RGB LED demo core.")
276+
parser.add_target_argument("--with-demo-io", action="store_true", help="Mirror demo signals to the Teensy-style edge connector.")
277+
parser.add_target_argument("--with-demo-scope", action="store_true", help="Add LiteScope Analyzer probes for the demo core.")
278+
parser.add_target_argument("--demo-scope-depth", default=512, type=int, help="LiteScope Analyzer capture depth.")
279+
parser.add_target_argument("--demo-analyzer-csv", default=None, help="LiteScope Analyzer CSV path.")
142280
parser.set_defaults(
143281
cpu_type = "None",
144282
integrated_sram_size = 0,
@@ -148,12 +286,20 @@ def main():
148286
with_uartbone = False)
149287
args = parser.parse_args()
150288

289+
builder_argdict = parser.builder_argdict
290+
output_dir = builder_argdict["output_dir"] or "build/adiuvo_forgix"
291+
demo_analyzer_csv = args.demo_analyzer_csv or os.path.join(output_dir, "analyzer.csv")
292+
151293
soc = BaseSoC(
152-
sys_clk_freq = args.sys_clk_freq,
153-
with_spibone = args.with_spibone,
154-
with_demo_leds = args.with_demo_leds,
294+
sys_clk_freq = args.sys_clk_freq,
295+
with_spibone = args.with_spibone,
296+
with_demo_leds = args.with_demo_leds,
297+
with_demo_io = args.with_demo_io,
298+
with_demo_scope = args.with_demo_scope,
299+
demo_scope_depth = args.demo_scope_depth,
300+
demo_analyzer_csv = demo_analyzer_csv,
155301
**parser.soc_argdict)
156-
builder = Builder(soc, **parser.builder_argdict)
302+
builder = Builder(soc, **builder_argdict)
157303
if args.build:
158304
builder.build(**parser.toolchain_argdict)
159305

0 commit comments

Comments
 (0)