66# Copyright (c) 2026 Florent Kermarrec <florent@enjoy-digital.fr>
77# SPDX-License-Identifier: BSD-2-Clause
88
9+ import os
10+
911from migen import *
1012
1113from litex .gen import *
@@ -39,41 +41,97 @@ def __init__(self, platform, sys_clk_freq):
3941# DemoLEDs -----------------------------------------------------------------------------------------
4042
4143class 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
102197class 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
136270def 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