From 6588a4c9b80f90decfc957f388713392765e6dc8 Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Fri, 3 Apr 2026 14:19:32 +0200 Subject: [PATCH 1/2] top/xbeam/persist: add probabilistic decay skip --- gateware/src/rs/hal/src/persist.rs | 5 ++++ gateware/src/tiliqua/raster/persist.py | 30 +++++++++++++++++++++--- gateware/src/top/xbeam/fw/src/main.rs | 13 ++++++---- gateware/src/top/xbeam/fw/src/options.rs | 3 +++ gateware/src/top/xbeam/top.py | 9 +++---- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/gateware/src/rs/hal/src/persist.rs b/gateware/src/rs/hal/src/persist.rs index 282565b9..ee2f6f7e 100644 --- a/gateware/src/rs/hal/src/persist.rs +++ b/gateware/src/rs/hal/src/persist.rs @@ -1,6 +1,7 @@ pub trait Persist { fn set_persist(&mut self, value: u16); fn set_decay(&mut self, value: u8); + fn set_skip(&mut self, value: u8); } #[macro_export] @@ -29,6 +30,10 @@ macro_rules! impl_persist { self.registers.decay().write(|w| unsafe { w.decay().bits(value) } ); } + fn set_skip(&mut self, value: u8) { + self.registers.skip().write(|w| unsafe { w.skip().bits(value) } ); + } + } )+ }; diff --git a/gateware/src/tiliqua/raster/persist.py b/gateware/src/tiliqua/raster/persist.py index 45393f84..de66d114 100644 --- a/gateware/src/tiliqua/raster/persist.py +++ b/gateware/src/tiliqua/raster/persist.py @@ -32,6 +32,7 @@ def __init__(self, *, bus_signature, # Tweakables "holdoff": In(16, init=holdoff_default), "decay": In(4, init=1), + "skip": In(8, init=0), # DMA bus / fb "bus": Out(bus_signature), "fbp": In(DMAFramebuffer.Properties()), @@ -70,11 +71,21 @@ def elaborate(self, platform) -> Module: # Latched version of decay speed control input decay_latch = Signal.like(self.decay) + # Latched version of skip probability control input + skip_latch = Signal.like(self.skip) # Track delay between read/write bursts holdoff_count = Signal(32) # Incoming pixel array (read from FIFO) pixels_r = Signal(data.ArrayLayout(Pixel, 4)) + # Free-running LFSR for probabilistic pixel skipping. + lfsr0 = Signal(unsigned(32), init=0x67452301) + lfsr1 = Signal(unsigned(32), init=0xefcdab89) + lfsr1_next = Signal(unsigned(32)) + m.d.comb += lfsr1_next.eq(lfsr1 + lfsr0) + m.d.sync += lfsr1.eq(lfsr1_next) + m.d.sync += lfsr0.eq(lfsr0 ^ lfsr1_next) + m.d.comb += self.fifo.w_data.eq(bus.dat_r) # Used for fastpath when all pixels are zero @@ -101,6 +112,7 @@ def elaborate(self, platform) -> Module: with m.State('BURST-IN'): m.d.sync += decay_latch.eq(self.decay) + m.d.sync += skip_latch.eq(self.skip) m.d.comb += [ bus.stb.eq(1), bus.cyc.eq(1), @@ -135,12 +147,17 @@ def elaborate(self, platform) -> Module: with m.State('BURST-OUT'): # The actual persistance calculation. 4 pixels at a time. + # + # Per-pixel LFSR comparison decides whether to decay or + # write back unchanged (probabilistic skip). pixels_w = Signal(data.ArrayLayout(Pixel, 4)) for n in range(4): - # color + skip_this = Signal(name=f"skip_{n}") + m.d.comb += skip_this.eq(lfsr1[n*8:(n*8)+8] < skip_latch) m.d.comb += pixels_w[n].color.eq(pixels_r[n].color) - # intensity - with m.If(pixels_r[n].intensity >= decay_latch): + with m.If(skip_this): + m.d.comb += pixels_w[n].intensity.eq(pixels_r[n].intensity) + with m.Elif(pixels_r[n].intensity >= decay_latch): m.d.comb += pixels_w[n].intensity.eq(pixels_r[n].intensity - decay_latch) with m.Else(): m.d.comb += pixels_w[n].intensity.eq(0) @@ -184,6 +201,9 @@ class PersistReg(csr.Register, access="w"): class DecayReg(csr.Register, access="w"): decay: csr.Field(csr.action.W, unsigned(8)) + class SkipReg(csr.Register, access="w"): + skip: csr.Field(csr.action.W, unsigned(8)) + def __init__(self, bus_dma): self.en = Signal() self.persist = Persistance(bus_signature=bus_dma.bus.signature.flip()) @@ -193,6 +213,7 @@ def __init__(self, bus_dma): self._persist = regs.add("persist", self.PersistReg(), offset=0x0) self._decay = regs.add("decay", self.DecayReg(), offset=0x4) + self._skip = regs.add("skip", self.SkipReg(), offset=0x8) self._bridge = csr.Bridge(regs.as_memory_map()) @@ -216,4 +237,7 @@ def elaborate(self, platform): with m.If(self._decay.f.decay.w_stb): m.d.sync += self.persist.decay.eq(self._decay.f.decay.w_data) + with m.If(self._skip.f.skip.w_stb): + m.d.sync += self.persist.skip.eq(self._skip.f.skip.w_data) + return m diff --git a/gateware/src/top/xbeam/fw/src/main.rs b/gateware/src/top/xbeam/fw/src/main.rs index a0df6940..5e61c3c3 100644 --- a/gateware/src/top/xbeam/fw/src/main.rs +++ b/gateware/src/top/xbeam/fw/src/main.rs @@ -60,13 +60,14 @@ fn build_cc_mapper(opts: &Opts) -> MidiCcMapper { m.add(31, global_index(opts, &opts.delay.delay_y), CcMapMode::Absolute); m.add(32, global_index(opts, &opts.delay.delay_i), CcMapMode::Absolute); m.add(33, global_index(opts, &opts.delay.delay_c), CcMapMode::Absolute); - // Beam page (CC 40-45) + // Beam page (CC 40-46) m.add(40, global_index(opts, &opts.beam.persist), CcMapMode::Absolute); m.add(41, global_index(opts, &opts.beam.decay), CcMapMode::Absolute); - m.add(42, global_index(opts, &opts.beam.ui_hue), CcMapMode::Absolute); - m.add(43, global_index(opts, &opts.beam.palette), CcMapMode::Absolute); - m.add(44, global_index(opts, &opts.beam.grid), CcMapMode::Absolute); - m.add(45, global_index(opts, &opts.beam.grid_i), CcMapMode::Absolute); + m.add(42, global_index(opts, &opts.beam.skip), CcMapMode::Absolute); + m.add(43, global_index(opts, &opts.beam.ui_hue), CcMapMode::Absolute); + m.add(44, global_index(opts, &opts.beam.palette), CcMapMode::Absolute); + m.add(45, global_index(opts, &opts.beam.grid), CcMapMode::Absolute); + m.add(46, global_index(opts, &opts.beam.grid_i), CcMapMode::Absolute); // Misc page (CC 50-52) m.add(50, global_index(opts, &opts.misc.plot_type), CcMapMode::Absolute); m.add(51, global_index(opts, &opts.misc.plot_src), CcMapMode::Absolute); @@ -283,9 +284,11 @@ fn main() -> ! { opts.beam.ui_hue.value).ok(); persist.set_persist(128); persist.set_decay(1); + persist.set_skip(0); } else { persist.set_persist(opts.beam.persist.value); persist.set_decay(opts.beam.decay.value); + persist.set_skip(opts.beam.skip.value); } diff --git a/gateware/src/top/xbeam/fw/src/options.rs b/gateware/src/top/xbeam/fw/src/options.rs index 1a36ad9d..87503092 100644 --- a/gateware/src/top/xbeam/fw/src/options.rs +++ b/gateware/src/top/xbeam/fw/src/options.rs @@ -92,6 +92,7 @@ int_params!(DelayParams { step: 8, min: 0, max: 512, format: IntFormat int_params!(PCScaleParams { step: 1, min: 0, max: 15 }); int_params!(PersistParams { step: 32, min: 32, max: 4096 }); int_params!(DecayParams { step: 1, min: 0, max: 15 }); +int_params!(SkipParams { step: 16, min: 0, max: 240 }); int_params!(IntensityParams { step: 1, min: 0, max: 15 }); int_params!(HueParams { step: 1, min: 0, max: 15 }); int_params!(TriggerLvlParams { step: 500, min: -16000, max: 16000, format: IntFormat::Scaled { divisor: 4000, precision: 2, suffix: "V" } }); @@ -145,6 +146,8 @@ pub struct BeamOpts { pub persist: IntOption, #[option(1)] pub decay: IntOption, + #[option(0)] + pub skip: IntOption, #[option(10)] pub ui_hue: IntOption, #[option] diff --git a/gateware/src/top/xbeam/top.py b/gateware/src/top/xbeam/top.py index 66388da3..8ada8a05 100644 --- a/gateware/src/top/xbeam/top.py +++ b/gateware/src/top/xbeam/top.py @@ -106,10 +106,11 @@ BEAM persist 40 phosphor decay speed (high = slow) BEAM decay 41 phosphor decay amount (low = slow) - BEAM ui-hue 42 menu and grid overlay hue - BEAM palette 43 color palette - BEAM grid 44 grid overlay style - BEAM grid-i 45 grid overlay intensity + BEAM skip 42 probabilistic decay skip (high = more) + BEAM ui-hue 43 menu and grid overlay hue + BEAM palette 44 color palette + BEAM grid 45 grid overlay style + BEAM grid-i 46 grid overlay intensity MISC plot-type 50 vectorscope or oscilloscope MISC plot-src 51 plot inputs or outputs From dc277764f68ffa16b892810c7fbf18892dd105aa Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Fri, 3 Apr 2026 14:22:01 +0200 Subject: [PATCH 2/2] top/xbeam: s/skip/rnd-skip --- gateware/src/top/xbeam/fw/src/main.rs | 4 ++-- gateware/src/top/xbeam/fw/src/options.rs | 2 +- gateware/src/top/xbeam/top.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gateware/src/top/xbeam/fw/src/main.rs b/gateware/src/top/xbeam/fw/src/main.rs index 5e61c3c3..a896f573 100644 --- a/gateware/src/top/xbeam/fw/src/main.rs +++ b/gateware/src/top/xbeam/fw/src/main.rs @@ -63,7 +63,7 @@ fn build_cc_mapper(opts: &Opts) -> MidiCcMapper { // Beam page (CC 40-46) m.add(40, global_index(opts, &opts.beam.persist), CcMapMode::Absolute); m.add(41, global_index(opts, &opts.beam.decay), CcMapMode::Absolute); - m.add(42, global_index(opts, &opts.beam.skip), CcMapMode::Absolute); + m.add(42, global_index(opts, &opts.beam.rnd_skip), CcMapMode::Absolute); m.add(43, global_index(opts, &opts.beam.ui_hue), CcMapMode::Absolute); m.add(44, global_index(opts, &opts.beam.palette), CcMapMode::Absolute); m.add(45, global_index(opts, &opts.beam.grid), CcMapMode::Absolute); @@ -288,7 +288,7 @@ fn main() -> ! { } else { persist.set_persist(opts.beam.persist.value); persist.set_decay(opts.beam.decay.value); - persist.set_skip(opts.beam.skip.value); + persist.set_skip(opts.beam.rnd_skip.value); } diff --git a/gateware/src/top/xbeam/fw/src/options.rs b/gateware/src/top/xbeam/fw/src/options.rs index 87503092..f7302cb6 100644 --- a/gateware/src/top/xbeam/fw/src/options.rs +++ b/gateware/src/top/xbeam/fw/src/options.rs @@ -147,7 +147,7 @@ pub struct BeamOpts { #[option(1)] pub decay: IntOption, #[option(0)] - pub skip: IntOption, + pub rnd_skip: IntOption, #[option(10)] pub ui_hue: IntOption, #[option] diff --git a/gateware/src/top/xbeam/top.py b/gateware/src/top/xbeam/top.py index 8ada8a05..24719cc5 100644 --- a/gateware/src/top/xbeam/top.py +++ b/gateware/src/top/xbeam/top.py @@ -106,7 +106,7 @@ BEAM persist 40 phosphor decay speed (high = slow) BEAM decay 41 phosphor decay amount (low = slow) - BEAM skip 42 probabilistic decay skip (high = more) + BEAM rnd-skip 42 probabilistic decay skip (high = more) BEAM ui-hue 43 menu and grid overlay hue BEAM palette 44 color palette BEAM grid 45 grid overlay style