From 7b8e448d87627f881efee50507db573e0a44ac30 Mon Sep 17 00:00:00 2001 From: Sebastian Holzapfel Date: Sun, 31 Mar 2024 21:30:56 +0200 Subject: [PATCH] fifo router works ch1 not ch0 --- example-ecpix-5.py | 9 +- firmware/litex-fw/src/main.rs | 120 ++++-------------- nominal.sh | 14 +++ rtl/dma_router.py | 231 ---------------------------------- rtl/dsp_wrapper.py | 176 -------------------------- rtl/eurorack_pmod_wrapper.py | 113 ++++++++++++++++- sim.py | 9 +- 7 files changed, 155 insertions(+), 517 deletions(-) create mode 100755 nominal.sh delete mode 100644 rtl/dma_router.py delete mode 100644 rtl/dsp_wrapper.py diff --git a/example-ecpix-5.py b/example-ecpix-5.py index 02fed62..06123ac 100755 --- a/example-ecpix-5.py +++ b/example-ecpix-5.py @@ -15,12 +15,11 @@ from litex.soc.interconnect import wishbone from litex.soc.interconnect.stream import ClockDomainCrossing from litex.soc.interconnect.csr_eventmanager import * +from litex.soc.integration.soc import SoCRegion from litex_boards.targets.lambdaconcept_ecpix5 import * from rtl.eurorack_pmod_wrapper import * -from rtl.dsp_wrapper import * -from rtl.dma_router import * _io_eurorack_pmod = [ ("eurorack_pmod_p0", 0, @@ -53,9 +52,9 @@ def add_eurorack_pmod(soc, sample_rate=48000, dma_output_capable=True): eurorack_pmod_pads = soc.platform.request("eurorack_pmod_p0") eurorack_pmod = EurorackPmod(soc.platform, eurorack_pmod_pads) soc.add_module("eurorack_pmod0", eurorack_pmod) - - # Now instantiate the DMA router and connect it to the EurorackPmod. - add_dma_router(soc, eurorack_pmod, output_capable=dma_output_capable) + soc.bus.add_slave("i2s", eurorack_pmod.bus, + SoCRegion(origin=0xb1000000, size=512*16, cached=False)) + soc.irq.add("eurorack_pmod0", use_loc_if_exists=True) def main(): from litex.build.parser import LiteXArgumentParser diff --git a/firmware/litex-fw/src/main.rs b/firmware/litex-fw/src/main.rs index 928a690..7b6690e 100644 --- a/firmware/litex-fw/src/main.rs +++ b/firmware/litex-fw/src/main.rs @@ -37,34 +37,17 @@ use dsp::*; /// Number of channels per section (4x input, 4x output) const N_CHANNELS: usize = 4; -/// Size of DMA buffers in 32-bit words. -const BUF_SZ_WORDS: usize = 512; - -/// Each sample is 16-bits wide, so each buffer can store twice as many samples. -const BUF_SZ_SAMPLES: usize = BUF_SZ_WORDS * 2; - -/// Each IRQ shall process half of the buffers as they are circularly serviced. -const BUF_SZ_SAMPLES_PER_IRQ: usize = BUF_SZ_SAMPLES / 2; - -// Note: these buffers MUST be word-aligned because the DMA engine iterates at word granularity. - -/// Static DMA buffer, circularly written to by the DMA engine to pipe samples IN from the eurorack-pmod. -static mut BUF_IN: Aligned = Aligned([0i16; BUF_SZ_SAMPLES]); - -/// Static DMA buffer, circularly read by the DMA engine to pipe samples OUT of the eurorack-pmod. -static mut BUF_OUT: Aligned = Aligned([0i16; BUF_SZ_SAMPLES]); - /// Some global state to track how long IRQs are taking to service. static mut LAST_IRQ: u32 = 0; static mut LAST_IRQ_LEN: u32 = 0; static mut LAST_IRQ_PERIOD: u32 = 0; -/// Persistent state required for LPF DSP operations. -static mut KARLSEN_LPF: Option = None; +static mut LAST_CH0: i16 = 0; +static mut LAST_CH1: i16 = 0; // Map the RISCV IRQ PLIC onto the fixed address present in the VexRISCV implementation. // TODO: ideally fetch this from the svf, its currently not exported by `svd2rust`! -riscv::plic_context!(PLIC0, litex_sys::PLIC_BASE, 0, VexInterrupt, VexPriority); +riscv::plic_context!(PLIC0, 0xf0c00000, 0, VexInterrupt, VexPriority); // Create the HAL bindings for the remaining LiteX peripherals. @@ -76,45 +59,9 @@ litex_hal::timer! { Timer: litex_pac::TIMER0, } - -/// Called once per IRQ to service the appropriate half of the DMA buffer. -/// Warn: this is run in IRQ context and blocks all IRQs while it is running. -/// You will want to re-think if this lives in a world with other IRQs. -fn process(dsp: &mut KarlsenLpf, buf_out: &mut [i16], buf_in: &[i16]) { - for i in 0..(buf_in.len()/N_CHANNELS) { - // Convert all input channels to an approprate fixed-point representation - let x_in: [Fix; N_CHANNELS] = [ - Fix::from_bits(buf_in[N_CHANNELS*i+0] as i32), - Fix::from_bits(buf_in[N_CHANNELS*i+1] as i32), - Fix::from_bits(buf_in[N_CHANNELS*i+2] as i32), - Fix::from_bits(buf_in[N_CHANNELS*i+3] as i32), - ]; - // Feed them into our DSP function - let y: Fix = dsp.proc(x_in[0], x_in[1], x_in[2]); - - // We only have 1 output, so use that for output 1 and just - // mirror inputs straight to outputs for the rest. - buf_out[N_CHANNELS*i+0] = y.to_bits() as i16; - buf_out[N_CHANNELS*i+1] = x_in[1].to_bits() as i16; - buf_out[N_CHANNELS*i+2] = x_in[2].to_bits() as i16; - buf_out[N_CHANNELS*i+3] = x_in[3].to_bits() as i16; - } -} - -/// Flush all VexRiscv caches, this is necessary for when we are writing to -/// a buffer which will be read by the DMA engine soon. -unsafe fn fence() { - asm!("fence iorw, iorw"); - // This magic instruction was just found in the LiteX source tree in - // the C implementation which is copied over for CSR operations. - // Without it, the caches aren't completely flushed. - asm!(".word(0x500F)"); -} - /// Handler for ALL IRQs. #[export_name = "DefaultHandler"] unsafe fn irq_handler() { - // First, claim the IRQ for this core. // This should only ever fail if we have multiple cores claiming irqs // in an SMP environment (which is not the case for this simple example). @@ -128,33 +75,22 @@ unsafe fn irq_handler() { LAST_IRQ = trace; match pending_irq.pac_irq { - pac::Interrupt::DMA_ROUTER0 => { - - // Read the current position in the circular DMA buffer to determine whether - // we need to service the first or last half of the DMA buffer. - - let offset = peripherals.DMA_ROUTER0.offset_words().read().bits(); - let pending_subtype = peripherals.DMA_ROUTER0.ev_pending().read().bits(); - - if let Some(ref mut dsp) = KARLSEN_LPF { - if offset as usize == ((BUF_SZ_WORDS/2)+1) { - fence(); - process(dsp, - &mut BUF_OUT[0..(BUF_SZ_SAMPLES/2)], - &BUF_IN[0..(BUF_SZ_SAMPLES/2)]); - } - - if offset as usize == (BUF_SZ_WORDS-1) { - fence(); - process(dsp, - &mut BUF_OUT[(BUF_SZ_SAMPLES/2)..(BUF_SZ_SAMPLES)], - &BUF_IN[(BUF_SZ_SAMPLES/2)..(BUF_SZ_SAMPLES)]); - } - } + pac::Interrupt::EURORACK_PMOD0 => { + + let pending_subtype = peripherals.EURORACK_PMOD0.ev_pending().read().bits(); - peripherals.DMA_ROUTER0.ev_pending().write(|w| w.bits(pending_subtype)); + while peripherals.EURORACK_PMOD0.rlevel().read().bits() > 8 { + let rdat = core::ptr::read_volatile(0xb100_0000 as *mut u32); + let ch0raw = rdat as i16; + let ch1raw = (rdat >> 16) as i16; + let ch0 = Fix::from_bits(ch0raw.into()); + let ch1 = Fix::from_bits(ch1raw.into()); - fence(); + LAST_CH0 = ch0raw; + LAST_CH1 = ch1raw; + } + + peripherals.EURORACK_PMOD0.ev_pending().write(|w| w.bits(pending_subtype)); }, _ => { // We shouldn't have any other types of IRQ... @@ -181,21 +117,11 @@ fn main() -> ! { let mut timer = Timer::new(peripherals.TIMER0, litex_sys::CONFIG_CLOCK_FREQUENCY); unsafe { - // Create an instance of our DSP function which has some internal state. - // Rust irq / scoped irq crates are neater for this kind of thing but - // using statics to reduce dependencies / make it obvious what this is doing. - KARLSEN_LPF = Some(KarlsenLpf::new()); - - // Configure the DMA engine such that it uses our static buffers, and start it. - peripherals.DMA_ROUTER0.base_writer().write(|w| w.bits(BUF_IN.as_mut_ptr() as u32)); - peripherals.DMA_ROUTER0.base_reader().write(|w| w.bits(BUF_OUT.as_ptr() as u32)); - peripherals.DMA_ROUTER0.length_words().write(|w| w.bits(BUF_SZ_WORDS as u32)); - peripherals.DMA_ROUTER0.enable().write(|w| w.bits(1u32)); - peripherals.DMA_ROUTER0.ev_enable().write(|w| w.half().bit(true)); + peripherals.EURORACK_PMOD0.ev_enable().write(|w| w.almost_full().bit(true)); // RISC-V PLIC configuration. let mut plic = PLIC0::new(); - let dma_irq = VexInterrupt::from(pac::Interrupt::DMA_ROUTER0); + let dma_irq = VexInterrupt::from(pac::Interrupt::EURORACK_PMOD0); plic.set_threshold(VexPriority::from(0)); plic.set_priority(dma_irq, VexPriority::from(1)); plic.enable_interrupt(dma_irq); @@ -209,12 +135,8 @@ fn main() -> ! { loop { unsafe { - fence(); - - // Print the current value of every input and output channel. - for i in 0..4 { - log::info!("{:x}@{:x},{:x}", i, BUF_IN[i], BUF_OUT[i]); - } + log::info!("ch0: {}", LAST_CH0); + log::info!("ch1: {}", LAST_CH1); // Print out some metrics as to how long our DSP operations are taking. log::info!("irq_period: {}", LAST_IRQ_PERIOD); diff --git a/nominal.sh b/nominal.sh new file mode 100755 index 0000000..b21acff --- /dev/null +++ b/nominal.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +# SVD only +echo "Generate SVD" +./sim.py --with-sdram --cpu-type vexriscv_smp --cpu-variant standard --csr-svd build/sim/csr.svd --no-compile-gateware --timer-uptime +# FW only +echo "Build FW" +cd firmware +OBJCOPY=riscv64-linux-gnu-objcopy BOARD=sim ./build.sh +cd .. +# Boot firmware after BIOS +echo "Simulate GW+FW" +./sim.py --with-sdram --sdram-init build/sim/rust-fw.bin --cpu-type vexriscv_smp --cpu-variant standard --timer-uptime --gtkwave-savefile --trace --trace-fst diff --git a/rtl/dma_router.py b/rtl/dma_router.py deleted file mode 100644 index 341c6d3..0000000 --- a/rtl/dma_router.py +++ /dev/null @@ -1,231 +0,0 @@ -#!/bin/python3 - -from migen import * - -from litex.soc.cores.dma import * -from litex.soc.interconnect.stream import ClockDomainCrossing -from litex.soc.interconnect.csr import * -from litex.soc.interconnect import wishbone -from litex.soc.interconnect.csr_eventmanager import * - -class DMARouter(LiteXModule): - - def __init__(self, soc, output_capable): - - self.output_capable = output_capable - - self.sink = stream.Endpoint([("in0", 16), - ("in1", 16), - ("in2", 16), - ("in3", 16)]) - self.writer_bus0 = wishbone.Interface() - self.submodules.dma_writer0 = WishboneDMAWriter(self.writer_bus0, endianness="big") - - if self.output_capable: - self.source = stream.Endpoint([("out0", 16), - ("out1", 16), - ("out2", 16), - ("out3", 16)]) - self.reader_bus0 = wishbone.Interface() - self.submodules.dma_reader0 = WishboneDMAReader(self.reader_bus0, endianness="big", fifo_depth=1) - - def add_csr(self): - # CSR - self._base_writer = CSRStorage(32) - if self.output_capable: - self._base_reader = CSRStorage(32) - self._length_words = CSRStorage(32, reset=0) - self._offset_words = CSRStatus(32) - self._enable = CSRStorage(reset=0) - - # Local signals - shift = log2_int(self.writer_bus0.data_width//8) - base_writer = Signal(self.writer_bus0.adr_width) - if self.output_capable: - base_reader = Signal(self.writer_bus0.adr_width) - offset_words = Signal(self.writer_bus0.adr_width) - length_words = Signal(self.writer_bus0.adr_width) - offset_words_r= Signal(self.writer_bus0.adr_width) - - self.comb += [ - base_writer.eq(self._base_writer.storage[shift:]), - length_words.eq(self._length_words.storage), - self._offset_words.status.eq(offset_words), - ] - - if self.output_capable: - self.comb += [ - base_reader.eq(self._base_reader.storage[shift:]), - ] - - # IRQ logic - self.ev = EventManager() - self.ev.half = EventSourceProcess(edge="rising") - self.comb += [ - self.ev.half.trigger.eq( - (offset_words == (length_words >> 1)) | - (offset_words == (length_words - 2))), - ] - self.ev.finalize() - - # DMA FSM (write side) - - fsm_write = FSM(reset_state="IDLE") - fsm_write = ResetInserter()(fsm_write) - self.submodules += fsm_write - self.comb += fsm_write.reset.eq(~self._enable.storage) - - fsm_write.act("IDLE", - NextValue(offset_words, 0), - NextState("EVEN"), - ) - - fsm_write.act("EVEN", - self.dma_writer0.sink.valid.eq(self.sink.valid), - self.dma_writer0.sink.address.eq(base_writer + offset_words), - self.dma_writer0.sink.data.eq((self.sink.in1 << 16) | self.sink.in0), - self.sink.ready.eq(self.dma_writer0.sink.ready), - If(self.sink.valid & self.sink.ready, - NextValue(offset_words, offset_words + 1), - NextState("ODD"), - ) - ) - - fsm_write.act("ODD", - self.dma_writer0.sink.valid.eq(self.sink.valid), - self.dma_writer0.sink.address.eq(base_writer + offset_words), - self.dma_writer0.sink.data.eq((self.sink.in3 << 16) | self.sink.in2), - self.sink.ready.eq(0), - If(self.sink.valid & self.dma_writer0.sink.ready, - NextValue(offset_words, offset_words + 1), - If((offset_words + 1) == length_words, - NextValue(offset_words, 0) - ), - NextState("EVEN"), - ) - ) - - # DMA FSM (read side) - if self.output_capable: - fsm_read = FSM(reset_state="IDLE") - fsm_read = ResetInserter()(fsm_read) - self.submodules += fsm_read - self.comb += fsm_read.reset.eq(~self._enable.storage) - - fsm_read.act("IDLE", - NextValue(offset_words_r, 0), - NextState("EVEN"), - ) - - fsm_read.act("EVEN", - NextValue(self.dma_reader0.sink.valid, 1), - NextValue(self.dma_reader0.sink.address, base_reader + offset_words_r), - If(self.dma_reader0.sink.ready, - NextValue(self.dma_reader0.sink.valid, 0), - NextValue(self.dma_reader0.source.ready, 1), - NextValue(offset_words_r, offset_words_r + 1), - NextState("WAIT1"), - ), - ) - - fsm_read.act("WAIT1", - If(self.dma_reader0.source.valid, - NextValue(self.dma_reader0.source.ready, 0), - NextValue(self.source.out0, self.dma_reader0.source.data[:16]), - NextValue(self.source.out1, self.dma_reader0.source.data[16:32]), - NextState("ODD"), - ) - ) - - fsm_read.act("ODD", - NextValue(self.dma_reader0.sink.valid, 1), - NextValue(self.dma_reader0.sink.address, base_reader + offset_words_r), - If(self.dma_reader0.sink.ready, - NextValue(self.dma_reader0.sink.valid, 0), - NextValue(self.dma_reader0.source.ready, 1), - NextValue(offset_words_r, offset_words_r + 1), - NextState("WAIT2"), - ), - ) - - - fsm_read.act("WAIT2", - If(self.dma_reader0.source.valid, - NextValue(self.source.valid, 1), - NextValue(self.dma_reader0.source.ready, 0), - NextValue(self.source.out2, self.dma_reader0.source.data[:16]), - NextValue(self.source.out3, self.dma_reader0.source.data[16:32]), - NextState("WAIT3"), - ) - ) - - fsm_read.act("WAIT3", - If(self.source.ready, - NextValue(self.source.valid, 0), - If(offset_words_r == length_words, - NextValue(offset_words_r, 0) - ), - NextState("EVEN"), - ) - ) - - -def add_dma_router(soc, eurorack_pmod, output_capable): - - soc.submodules.dma_router0 = DMARouter(soc, output_capable=output_capable) - soc.dma_router0.add_csr() - - # CDC - cdc_in0 = ClockDomainCrossing( - layout=[("in0", 16), - ("in1", 16), - ("in2", 16), - ("in3", 16)], - cd_from="clk_fs", - cd_to="sys" - ) - soc.add_module("cdc_in0", cdc_in0) - - soc.comb += [ - # CDC <-> I2S (clk_fs domain) - # ADC -> CDC - cdc_in0.sink.valid.eq(1), - cdc_in0.sink.in0.eq(eurorack_pmod.cal_in0), - cdc_in0.sink.in1.eq(eurorack_pmod.cal_in1), - cdc_in0.sink.in2.eq(eurorack_pmod.cal_in2), - cdc_in0.sink.in3.eq(eurorack_pmod.cal_in3), - - # ADC -> CDC -> Router -> DRAM - cdc_in0.source.connect(soc.dma_router0.sink), - ] - - soc.bus.add_master(master=soc.dma_router0.dma_writer0.bus) - - if output_capable: - cdc_out0 = ClockDomainCrossing( - layout=[("out0", 16), - ("out1", 16), - ("out2", 16), - ("out3", 16)], - cd_from="sys", - cd_to="clk_fs" - ) - soc.add_module("cdc_out0", cdc_out0) - - soc.comb += [ - # CDC <-> I2S (clk_fs domain) - # CDC -> DAC - cdc_out0.source.ready.eq(1), - eurorack_pmod.cal_out0.eq(cdc_out0.source.out0), - eurorack_pmod.cal_out1.eq(cdc_out0.source.out1), - eurorack_pmod.cal_out2.eq(cdc_out0.source.out2), - eurorack_pmod.cal_out3.eq(cdc_out0.source.out3), - - # DRAM -> Router -> CDC -> DAC - soc.dma_router0.source.connect(cdc_out0.sink), - ] - - soc.bus.add_master(master=soc.dma_router0.dma_reader0.bus) - - soc.irq.add("dma_router0", use_loc_if_exists=True) - diff --git a/rtl/dsp_wrapper.py b/rtl/dsp_wrapper.py deleted file mode 100644 index 23d1b76..0000000 --- a/rtl/dsp_wrapper.py +++ /dev/null @@ -1,176 +0,0 @@ -import os - -from migen import * - -from litex.soc.interconnect.csr import * - -SOURCES_ROOT = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "../deps/eurorack-pmod/gateware" - ) - -class WavetableOscillator(Module, AutoCSR): - def __init__(self, platform, w=16): - self.w = w - self.wavetable_path = os.path.join(SOURCES_ROOT, "cores/util/vco/wavetable.hex") - self.wavetable_size = 256 - - # Exposed signals - - self.rst = ResetSignal() - self.clk_fs = ClockSignal("clk_fs") - - self.wavetable_inc = Signal(32) - self.out = Signal((w, True)) - - platform.add_verilog_include_path(SOURCES_ROOT) - platform.add_sources(SOURCES_ROOT, "cores/util/wavetable_osc.sv") - - self.specials += Instance("wavetable_osc", - - # Parameters - p_W = self.w, - p_WAVETABLE_PATH = self.wavetable_path, - p_WAVETABLE_SIZE = self.wavetable_size, - - # Ports (clk + reset) - i_rst = self.rst, - i_sample_clk = self.clk_fs, - - # Ports (filter parameters) - i_wavetable_inc = self.wavetable_inc, - o_out = self.out, - ) - - # Exposed CSRs - - self.csr_wavetable_inc = CSRStorage(32) - - self.comb += [ - self.wavetable_inc.eq(self.csr_wavetable_inc.storage), - ] - -class KarlsenLowPass(Module, AutoCSR): - def __init__(self, platform, w=16): - self.w = w - - # Exposed signals - - self.rst = ResetSignal() - self.clk_256fs = ClockSignal("clk_256fs") - self.clk_fs = ClockSignal("clk_fs") - - self.g = Signal((w, True)) - self.resonance = Signal((w, True)) - self.sample_in = Signal((w, True)) - self.sample_out = Signal((w, True)) - - # Verilog sources - - platform.add_verilog_include_path(SOURCES_ROOT) - platform.add_sources(SOURCES_ROOT, "cores/util/filter/karlsen_lpf_pipelined.sv") - - self.specials += Instance("karlsen_lpf_pipelined", - - # Parameters - p_W = self.w, - - # Ports (clk + reset) - i_rst = self.rst, - i_clk = self.clk_256fs, - i_sample_clk = self.clk_fs, - - # Ports (filter parameters) - i_g = self.g, - i_resonance = self.resonance, - - # Ports (audio in/out) - i_sample_in = self.sample_in, - o_sample_out = self.sample_out, - ) - - - # Exposed CSRs - - self.csr_g = CSRStorage(16) - self.csr_resonance = CSRStorage(16) - - self.comb += [ - self.g.eq(self.csr_g.storage), - self.resonance.eq(self.csr_resonance.storage), - ] - -class DcBlock(Module): - def __init__(self, platform, w=16): - self.w = w - - # Exposed signals - - self.clk_fs = ClockSignal("clk_fs") - - self.sample_in = Signal((w, True)) - self.sample_out = Signal((w, True)) - - # Verilog sources - - platform.add_verilog_include_path(SOURCES_ROOT) - platform.add_sources(SOURCES_ROOT, "cores/util/dc_block.sv") - - self.specials += Instance("dc_block", - - # Parameters - p_W = self.w, - - # Ports (clk + reset) - i_sample_clk = self.clk_fs, - - # Ports (audio in/out) - i_sample_in = self.sample_in, - o_sample_out = self.sample_out, - ) - -class PitchShift(Module, AutoCSR): - def __init__(self, platform, window_size=512, xfade_size=128, w=16): - - self.window_size = window_size - self.xfade_size = xfade_size - self.w = w - - # Exposed signals - - self.clk_fs = ClockSignal("clk_fs") - - self.sample_in = Signal((w, True)) - self.pitch = Signal((w, True)) - self.sample_out = Signal((w, True)) - - # Verilog sources - - platform.add_verilog_include_path(SOURCES_ROOT) - platform.add_sources(SOURCES_ROOT, "cores/util/transpose.sv") - platform.add_sources(SOURCES_ROOT, "cores/util/delayline.sv") - - self.specials += Instance("transpose", - - # Parameters - p_W = self.w, - p_WINDOW = self.window_size, - p_XFADE = self.xfade_size, - - - # Ports (clk + reset) - i_sample_clk = self.clk_fs, - - # Ports (audio in/out) - i_sample_in = self.sample_in, - i_pitch = self.pitch, - o_sample_out = self.sample_out, - ) - - # Exposed CSRs - - self.csr_pitch = CSRStorage(16) - - self.comb += [ - self.pitch.eq(self.csr_pitch.storage), - ] diff --git a/rtl/eurorack_pmod_wrapper.py b/rtl/eurorack_pmod_wrapper.py index 6415b09..5576194 100644 --- a/rtl/eurorack_pmod_wrapper.py +++ b/rtl/eurorack_pmod_wrapper.py @@ -6,13 +6,121 @@ from litex.soc.cores.gpio import GPIOOut +from litex.soc.cores.dma import * +from litex.soc.interconnect.stream import ClockDomainCrossing +from litex.soc.interconnect.csr import * +from litex.soc.interconnect import wishbone +from litex.soc.interconnect.csr_eventmanager import * + SOURCES_ROOT = os.path.join( os.path.dirname(os.path.realpath(__file__)), "../deps/eurorack-pmod/gateware" ) class EurorackPmod(Module, AutoCSR): - def __init__(self, platform, pads, w=16, output_csr_read_only=True, sim=False): + def add_fifos(self, depth=512): + + layout_rfifo = [("in0", 16), + ("in1", 16), + ("in2", 16), + ("in3", 16)] + + cdc_in0 = ClockDomainCrossing( + layout=layout_rfifo, + cd_from="clk_fs", + cd_to="sys" + ) + self.submodules += cdc_in0 + + rfifo = stream.SyncFIFO(layout_rfifo, depth) + self.submodules += rfifo + rfifo_almost_full = rfifo.level > (depth - 4) + + self.rlevel = CSRStatus(16) + self.comb += [ + self.rlevel.status.eq(rfifo.level) + ] + + # IRQ logic + self.ev = EventManager() + self.ev.almost_full = EventSourceProcess(edge="rising") + self.submodules += self.ev + self.submodules += self.ev.almost_full + self.comb += [ + self.ev.almost_full.trigger.eq(rfifo_almost_full), + ] + self.ev.finalize() + + self.comb += [ + # CDC <-> I2S (clk_fs domain) + # ADC -> CDC + cdc_in0.sink.valid.eq(1), + cdc_in0.sink.in0.eq(self.cal_in0), + cdc_in0.sink.in1.eq(self.cal_in1), + cdc_in0.sink.in2.eq(self.cal_in2), + cdc_in0.sink.in3.eq(self.cal_in3), + + # ADC -> CDC -> Router -> DRAM + cdc_in0.source.connect(rfifo.sink), + ] + + self.bus = bus = wishbone.Interface(data_width=32, address_width=32, addressing="word") + rd_ack = Signal() + wr_ack = Signal() + self.comb += [ + If(bus.we, + bus.ack.eq(wr_ack), + ).Else( + bus.ack.eq(rd_ack), + ) + ] + + bus_read = Signal() + bus_read_d = Signal() + rd_ack_pipe = Signal() + self.comb += bus_read.eq(bus.cyc & bus.stb & ~bus.we & (bus.cti == 0)) + self.sync += [ # This is the bus responder -- only works for uncached memory regions + bus_read_d.eq(bus_read), + If(bus_read & ~bus_read_d, # One response, one cycle + rd_ack_pipe.eq(1), + If(rfifo.level != 0, + bus.dat_r.eq(rfifo.source.payload.in0 | rfifo.source.payload.in1 << 16), + rfifo.source.ready.eq(1), + ).Else( + # Don't stall the bus indefinitely if we try to read from an empty fifo...just + # return garbage + bus.dat_r.eq(0xdeadbeef), + rfifo.source.ready.eq(0), + ) + ).Else( + rfifo.source.ready.eq(0), + rd_ack_pipe.eq(0), + ), + rd_ack.eq(rd_ack_pipe), + ] + + # BUS responder // WRITE side + """ + self.sync += [ + # This is the bus responder -- need to check how this interacts with uncached memory + # region + If(bus.cyc & bus.stb & bus.we & ~bus.ack, + If(~fifo.full, + fifo.wr_d.eq(bus.dat_w), + fifo.wren.eq(~tx_reset), + wr_ack.eq(1), + ).Else( + fifo.wren.eq(0), + wr_ack.eq(0), + ) + ).Else( + fifo.wren.eq(0), + wr_ack.eq(0), + ) + ] + """ + + def __init__(self, platform, pads, w=16, output_csr_read_only=True, with_fifos=True, sim=False): self.w = w self.cal_mem_file = os.path.join(SOURCES_ROOT, "cal/cal_mem.hex") self.codec_cfg_file = os.path.join(SOURCES_ROOT, "drivers/ak4619-cfg.hex") @@ -191,3 +299,6 @@ def __init__(self, platform, pads, w=16, output_csr_read_only=True, sim=False): self.cal_out2.eq(self.csr_cal_out2.storage), self.cal_out3.eq(self.csr_cal_out3.storage), ] + + if with_fifos: + self.add_fifos() diff --git a/sim.py b/sim.py index 9f4f332..4954391 100755 --- a/sim.py +++ b/sim.py @@ -10,10 +10,9 @@ from litex.soc.interconnect import wishbone from litex.soc.interconnect.stream import ClockDomainCrossing from litex.soc.interconnect.csr_eventmanager import * +from litex.soc.integration.soc import SoCRegion from rtl.eurorack_pmod_wrapper import * -from rtl.dsp_wrapper import * -from rtl.dma_router import * CLK_FREQ_SYS = 5e6 CLK_FREQ_256FS = 1e6 @@ -49,9 +48,9 @@ def add_eurorack_pmod(soc): eurorack_pmod_pads = soc.platform.request("eurorack_pmod_p0") eurorack_pmod = EurorackPmod(soc.platform, eurorack_pmod_pads, sim=True) soc.add_module("eurorack_pmod0", eurorack_pmod) - - # Now instantiate the DMA router and connect it to the EurorackPmod. - add_dma_router(soc, eurorack_pmod, output_capable=output_capable) + soc.bus.add_slave("i2s", eurorack_pmod.bus, + SoCRegion(origin=0xb1000000, size=512*16, cached=False)) + soc.irq.add("eurorack_pmod0", use_loc_if_exists=True) def sim_soc_extension(sim_config, soc): soc.platform.add_extension(_io_extra_clockers)