diff --git a/gateware/src/rs/hal/src/persist.rs b/gateware/src/rs/hal/src/persist.rs index 282565b9..a0672ca0 100644 --- a/gateware/src/rs/hal/src/persist.rs +++ b/gateware/src/rs/hal/src/persist.rs @@ -1,6 +1,11 @@ +/// Unified persistence control. +/// +/// Maps a single 1-80 value to decay, holdoff and probabilistic skip: +/// 1-15: decay ramps 15->1, holdoff=32, skip=0 +/// 16-64: decay=1, holdoff=32, skip ramps up +/// 65-80: decay=1, holdoff ramps 32->256, skip continues ramping pub trait Persist { - fn set_persist(&mut self, value: u16); - fn set_decay(&mut self, value: u8); + fn set_persistence(&mut self, value: u8); } #[macro_export] @@ -18,10 +23,8 @@ macro_rules! impl_persist { pub fn new(registers: $PACPERSISTX) -> Self { Self { registers } } - } - impl hal::persist::Persist for $PERSISTX { - fn set_persist(&mut self, value: u16) { + fn set_holdoff(&mut self, value: u16) { self.registers.persist().write(|w| unsafe { w.persist().bits(value) } ); } @@ -29,6 +32,30 @@ 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) } ); + } + } + + impl hal::persist::Persist for $PERSISTX { + fn set_persistence(&mut self, value: u8) { + let p = value as u32; + if p <= 15 { + self.set_decay((16 - p) as u8); + self.set_holdoff(32); + self.set_skip(0); + } else { + let t = p - 16; // 0..64 + self.set_decay(1); + self.set_skip(core::cmp::min(t << 2, 255) as u8); + if p <= 64 { + self.set_holdoff(32); + } else { + let h = p - 65; // 0..15 + self.set_holdoff(core::cmp::min(32 + (h << 5), 256) as u16); + } + } + } } )+ }; 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/bootloader/fw/src/main.rs b/gateware/src/top/bootloader/fw/src/main.rs index 5b00e9f7..e16d0138 100644 --- a/gateware/src/top/bootloader/fw/src/main.rs +++ b/gateware/src/top/bootloader/fw/src/main.rs @@ -849,7 +849,7 @@ fn main() -> ! { let mut logo_coord_ix = 0u32; let mut rng = fastrand::Rng::with_seed(0); - persist.set_persist(256); + persist.set_persistence(64); let stroke = PrimitiveStyleBuilder::new() .stroke_color(HI8::new(0, 10)) diff --git a/gateware/src/top/macro_osc/fw/src/main.rs b/gateware/src/top/macro_osc/fw/src/main.rs index d86da703..8afcd8df 100644 --- a/gateware/src/top/macro_osc/fw/src/main.rs +++ b/gateware/src/top/macro_osc/fw/src/main.rs @@ -360,11 +360,9 @@ fn main() -> ! { } if on_help_page { - persist.set_persist(256); - persist.set_decay(1); + persist.set_persistence(64); } else { - persist.set_persist(opts.beam.persist.value); - persist.set_decay(opts.beam.decay.value); + persist.set_persistence(opts.beam.persist.value); } vscope.set_hue(opts.beam.hue.value); diff --git a/gateware/src/top/macro_osc/fw/src/options.rs b/gateware/src/top/macro_osc/fw/src/options.rs index a1aa2009..bf8af551 100644 --- a/gateware/src/top/macro_osc/fw/src/options.rs +++ b/gateware/src/top/macro_osc/fw/src/options.rs @@ -63,8 +63,7 @@ int_params!(NoteParams { step: 1, min: 0, max: 128 }); int_params!(HarmonicsParams { step: 8, min: 0, max: 240 }); int_params!(TimbreParams { step: 8, min: 0, max: 240 }); int_params!(MorphParams { step: 8, min: 0, max: 240 }); -int_params!(PersistParams { step: 128, min: 128, max: 8192 }); -int_params!(DecayParams { step: 1, min: 0, max: 15 }); +int_params!(PersistParams { step: 1, min: 1, max: 80 }); 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" } }); @@ -113,10 +112,8 @@ pub struct VectorOpts { #[derive(OptionPage, Clone)] pub struct BeamOpts { - #[option(256)] + #[option(15)] pub persist: IntOption, - #[option(1)] - pub decay: IntOption, #[option(8)] pub intensity: IntOption, #[option(10)] diff --git a/gateware/src/top/polysyn/fw/src/main.rs b/gateware/src/top/polysyn/fw/src/main.rs index a1401975..cf2e4f01 100644 --- a/gateware/src/top/polysyn/fw/src/main.rs +++ b/gateware/src/top/polysyn/fw/src/main.rs @@ -217,10 +217,9 @@ fn build_cc_mapper(opts: &Opts) -> MidiCcMapper { // Effect page (CC 40-41) m.add(40, global_index(opts, &opts.effect.drive), CcMapMode::Absolute); m.add(41, global_index(opts, &opts.effect.diffuse), CcMapMode::Absolute); - // Beam page (CC 50-55) + // Beam page (CC 50-55, CC 52 formerly decay now unused) m.add(50, global_index(opts, &opts.beam.scale), CcMapMode::Absolute); m.add(51, global_index(opts, &opts.beam.persist), CcMapMode::Absolute); - m.add(52, global_index(opts, &opts.beam.decay), CcMapMode::Absolute); m.add(53, global_index(opts, &opts.beam.intensity), CcMapMode::Absolute); m.add(54, global_index(opts, &opts.beam.hue), CcMapMode::Absolute); m.add(55, global_index(opts, &opts.beam.palette), CcMapMode::Absolute); @@ -484,12 +483,10 @@ fn main() -> ! { v_active, opts.help.scroll.value, opts.beam.hue.value).ok(); - persist.set_persist(128); - persist.set_decay(1); + persist.set_persistence(64); vscope.set_enabled(false); } else { - persist.set_persist(opts.beam.persist.value); - persist.set_decay(opts.beam.decay.value); + persist.set_persistence(opts.beam.persist.value); vscope.set_enabled(true); } diff --git a/gateware/src/top/polysyn/fw/src/options.rs b/gateware/src/top/polysyn/fw/src/options.rs index 8acf96bb..8b43124c 100644 --- a/gateware/src/top/polysyn/fw/src/options.rs +++ b/gateware/src/top/polysyn/fw/src/options.rs @@ -83,8 +83,7 @@ int_params!(ResoParams { step: 2048, min: 0, max: 32768, format: IntF int_params!(DiffuseParams { step: 2048, min: 0, max: 32768, format: IntFormat::Scaled { divisor: 32768, precision: 2, suffix: "" } }); int_params!(AdsrTimeParams { step: 1024, min: 0, max: 16384, format: IntFormat::Scaled { divisor: 16384, precision: 2, suffix: "" } }); int_params!(AdsrLevelParams { step: 1024, min: 0, max: 32768, format: IntFormat::Scaled { divisor: 32768, precision: 2, suffix: "" } }); -int_params!(PersistParams { step: 32, min: 32, max: 4096 }); -int_params!(DecayParams { step: 1, min: 0, max: 15 }); +int_params!(PersistParams { step: 1, min: 1, max: 80 }); int_params!(IntensityParams { step: 1, min: 0, max: 15 }); int_params!(HueParams { step: 1, min: 0, max: 15 }); int_params!(ScrollParams { step: 1, min: 0, max: 125 }); @@ -139,10 +138,8 @@ pub struct AdsrOpts { pub struct BeamOpts { #[option(VScale::Scale2V)] pub scale: EnumOption, - #[option(32)] + #[option(15)] pub persist: IntOption, - #[option(1)] - pub decay: IntOption, #[option(8)] pub intensity: IntOption, #[option(10)] diff --git a/gateware/src/top/polysyn/top.py b/gateware/src/top/polysyn/top.py index 816bcb7b..c0dd0037 100644 --- a/gateware/src/top/polysyn/top.py +++ b/gateware/src/top/polysyn/top.py @@ -106,8 +106,8 @@ EFFECT diffuse 41 diffusion wet/dry mix BEAM scale 50 vectorscope volts/div - BEAM persist 51 phosphor decay speed (high = slow) - BEAM decay 52 phosphor decay amount (low = slow) + BEAM persist 51 phosphor persistence (high = slow) + - (CC 52 deprecated) BEAM intensity 53 trace intensity BEAM hue 54 trace and menu hue BEAM palette 55 color palette diff --git a/gateware/src/top/sampler/fw/src/main.rs b/gateware/src/top/sampler/fw/src/main.rs index d7715b76..70fd2d2b 100644 --- a/gateware/src/top/sampler/fw/src/main.rs +++ b/gateware/src/top/sampler/fw/src/main.rs @@ -312,11 +312,9 @@ fn main() -> ! { v_active, opts.help.scroll.value, hue).ok(); - persist.set_persist(128); - persist.set_decay(1); + persist.set_persistence(64); } else { - persist.set_persist(128); - persist.set_decay(1); + persist.set_persistence(32); } diff --git a/gateware/src/top/selftest/fw/src/main.rs b/gateware/src/top/selftest/fw/src/main.rs index f390fa4f..55dbdf40 100644 --- a/gateware/src/top/selftest/fw/src/main.rs +++ b/gateware/src/top/selftest/fw/src/main.rs @@ -480,7 +480,7 @@ fn main() -> ! { irq::scope(|s| { palette::ColorPalette::default().write_to_hardware(&mut display); - persist.set_persist(128); + persist.set_persistence(20); s.register(handlers::Interrupt::TIMER0, timer0); diff --git a/gateware/src/top/sid/fw/src/main.rs b/gateware/src/top/sid/fw/src/main.rs index 2e771901..ae34c6d0 100644 --- a/gateware/src/top/sid/fw/src/main.rs +++ b/gateware/src/top/sid/fw/src/main.rs @@ -272,7 +272,7 @@ fn main() -> ! { // Draw UI elements if on_help_page { - persist.set_persist(128); + persist.set_persistence(64); draw::draw_options(&mut display, &opts, h_active/2-30, v_active-100, hue).ok(); draw::draw_name(&mut display, h_active/2, v_active-50, hue, &bootinfo.manifest.name, &bootinfo.manifest.tag, &modeline).ok(); @@ -284,7 +284,7 @@ fn main() -> ! { opts.help.scroll.value, hue).ok(); } else { - persist.set_persist(64); + persist.set_persistence(15); draw::draw_options(&mut display, &opts, 100, v_active/2, hue).ok(); draw::draw_name(&mut display, h_active/2, v_active-50, hue, &bootinfo.manifest.name, &bootinfo.manifest.tag, &modeline).ok(); diff --git a/gateware/src/top/xbeam/fw/src/main.rs b/gateware/src/top/xbeam/fw/src/main.rs index a0df6940..7d9ed884 100644 --- a/gateware/src/top/xbeam/fw/src/main.rs +++ b/gateware/src/top/xbeam/fw/src/main.rs @@ -60,9 +60,8 @@ 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-45, CC 41 formerly decay now unused) 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); @@ -281,11 +280,9 @@ fn main() -> ! { v_active, opts.help.scroll.value, opts.beam.ui_hue.value).ok(); - persist.set_persist(128); - persist.set_decay(1); + persist.set_persistence(64); } else { - persist.set_persist(opts.beam.persist.value); - persist.set_decay(opts.beam.decay.value); + persist.set_persistence(opts.beam.persist.value); } diff --git a/gateware/src/top/xbeam/fw/src/options.rs b/gateware/src/top/xbeam/fw/src/options.rs index 1a36ad9d..cfbf51af 100644 --- a/gateware/src/top/xbeam/fw/src/options.rs +++ b/gateware/src/top/xbeam/fw/src/options.rs @@ -90,8 +90,7 @@ pub enum XZoom { int_params!(DelayParams { step: 8, min: 0, max: 512, format: IntFormat::Scaled { divisor: AUDIO_FS / 1000, precision: 1, suffix: "ms" } }); 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!(PersistParams { step: 1, min: 1, max: 80 }); 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" } }); @@ -141,10 +140,8 @@ pub struct DelayOpts { #[derive(OptionPage, Clone)] pub struct BeamOpts { - #[option(32)] + #[option(15)] pub persist: IntOption, - #[option(1)] - pub decay: 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..93eb52a3 100644 --- a/gateware/src/top/xbeam/top.py +++ b/gateware/src/top/xbeam/top.py @@ -104,8 +104,8 @@ DELAY delay-i 32 in2/intensity delayline length DELAY delay-c 33 in3/color delayline length - BEAM persist 40 phosphor decay speed (high = slow) - BEAM decay 41 phosphor decay amount (low = slow) + BEAM persist 40 phosphor persistence (high = slow) + - (CC 41 deprecated) BEAM ui-hue 42 menu and grid overlay hue BEAM palette 43 color palette BEAM grid 44 grid overlay style