nimphea/sys/sdram

Source   Edit  

External SDRAM support for libDaisy Nim wrapper

This module provides support for the external SDRAM chip on Daisy Seed. The SDRAM provides 64MB of additional RAM for audio buffers, delay lines, wavetables, and other large data structures.

Basic Usage:

import nimphea
import nimphea/sys/sdram

var hw = newDaisySeed()
var sdram = newSdramHandle()

# Large buffer in SDRAM
var delayBuffer {.codegenDecl: "$# $# __attribute__((section(\".sdram_bss\")))".}: array[480000, float32]

proc main() =
  hw.init()
  
  # Initialize SDRAM
  if sdram.init() != SDRAM_OK:
    return
  
  # Now you can use delayBuffer and other SDRAM variables
  for i in 0..<len(delayBuffer):
    delayBuffer[i] = 0.0
  
  while true:
    hw.delayMs(100)

SDRAM Memory Details:

  • Base Address: 0xC0000000
  • Size: 64MB (on Daisy Seed)
  • Speed: 100MHz
  • Chip: AS4C16M16SA or equivalent

Important Notes:

  • SDRAM must be initialized before accessing SDRAM variables
  • Use codegenDecl pragma with section attribute for SDRAM placement
  • SDRAM is not suitable for real-time audio processing (use internal RAM)
  • Perfect for buffers, wavetables, delay lines, sample storage

Delay Line Example:

const SAMPLE_RATE = 48000
const MAX_DELAY_SEC = 10

var delayLine {.sdramBss.}: array[SAMPLE_RATE * MAX_DELAY_SEC, float32]
var delayIndex = 0

proc audioCallback(input: ptr ptr cfloat, output: ptr ptr cfloat, size: csize_t) {.cdecl.} =
  for i in 0..<size:
    # Write to delay line
    delayLine[delayIndex] = input[0][i]
    
    # Read from delay line (simple)
    output[0][i] = delayLine[delayIndex]
    
    delayIndex = (delayIndex + 1) mod len(delayLine)

Wavetable Example:

const TABLE_SIZE = 2048
const NUM_TABLES = 64

var wavetables {.sdramBss.}: array[NUM_TABLES * TABLE_SIZE, float32]

proc initWavetables() =
  for tableNum in 0..<NUM_TABLES:
    for i in 0..<TABLE_SIZE:
      let phase = float32(i) / float32(TABLE_SIZE)
      let idx = tableNum * TABLE_SIZE + i
      wavetables[idx] = sin(phase * 2.0 * PI * float32(tableNum + 1))

Sample Buffer Example:

# Store 60 seconds of stereo audio at 48kHz
const BUFFER_SIZE = 48000 * 60 * 2

var sampleBuffer {.sdramBss.}: array[BUFFER_SIZE, float32]
var recordPos = 0
var playPos = 0

proc recordSample(left, right: float32) =
  if recordPos < BUFFER_SIZE - 1:
    sampleBuffer[recordPos] = left
    sampleBuffer[recordPos + 1] = right
    recordPos += 2

proc playSample(): tuple[left, right: float32] =
  if playPos < recordPos - 1:
    result.left = sampleBuffer[playPos]
    result.right = sampleBuffer[playPos + 1]
    playPos += 2
  else:
    result = (0.0'f32, 0.0'f32)

Types

SdramHandle {.importcpp: "SdramHandle".} = object
Source   Edit  
SdramMemoryInfo = object
  totalBytes*: int
  usedBytes*: int
  freeBytes*: int
  usedMegabytes*: float
  freeMegabytes*: float
  percentUsed*: float
Source   Edit  
SdramResult {.importcpp: "SdramHandle::Result", size: 4.} = enum
  SDRAM_OK = 0, SDRAM_ERR
Source   Edit  

Consts

SDRAM_BASE_ADDRESS = 0xC0000000'u32
Base address of SDRAM Source   Edit  
SDRAM_SIZE = 67108864
Total SDRAM size (64MB) Source   Edit  
SDRAM_SPEED = 100000000
SDRAM clock speed (100MHz) Source   Edit  

Procs

proc calcDelayBufferSize(sampleRate: float; delayTimeSeconds: float): int {.
    header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
Calculate the number of samples needed for a delay buffer Example: let size = calcDelayBufferSize(48000.0, 5.0) # 5 second delay Source   Edit  
proc calcWavetableSize(tableSize: int; numTables: int): int {.
    header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
Calculate total size for multiple wavetables Example: let size = calcWavetableSize(2048, 64) # 64 wavetables of 2048 samples Source   Edit  
proc clearSdramBss() {.header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}

Clear all SDRAM BSS memory to zero. This should be called after SDRAM initialization if you want to ensure all SDRAM variables start at zero.

Safety: The pointer arithmetic here is guaranteed safe because:

  • _ssdram_bss and _esdram_bss are linker symbols defined in the linker script and represent the start/end of the SDRAM BSS section
  • Both symbols are aligned to 4-byte (uint32) boundaries by the linker, enabling safe iteration by uint32 increments
  • The loop terminates at theEnd, guaranteeing no out-of-bounds access

Performance: This is a slow operation (may take several milliseconds on 64MB) but necessary for zeroing uninitialized SDRAM variables.

Source   Edit  
proc deInit(this: var SdramHandle): SdramResult {.importcpp: "#.DeInit(@)",
    header: "dev/sdram.h", header: "dev/sdram.h", ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc getMemoryInfo(): SdramMemoryInfo {.header: "dev/sdram.h", ...raises: [],
                                        tags: [], forbids: [].}
Get information about SDRAM memory usage Source   Edit  
proc getSdramAddress(): uint32 {.header: "dev/sdram.h", ...raises: [], tags: [],
                                 forbids: [].}
Get the base address of SDRAM Source   Edit  
proc getSdramBssSize(): int {.inline, header: "dev/sdram.h", ...raises: [],
                              tags: [], forbids: [].}
Get the size of used SDRAM BSS memory Source   Edit  
proc getSdramSize(): int {.header: "dev/sdram.h", ...raises: [], tags: [],
                           forbids: [].}
Get the total size of SDRAM in bytes Source   Edit  
proc init(this: var SdramHandle): SdramResult {.importcpp: "#.Init(@)",
    header: "dev/sdram.h", header: "dev/sdram.h", ...raises: [], tags: [],
    forbids: [].}
Source   Edit  
proc megabytesToSamples(megabytes: float; bytesPerSample: int = 4): int {.
    header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
Convert megabytes to number of samples (assumes float32 by default) Example: let samples = megabytesToSamples(10.0) # 10MB of float32 samples Source   Edit  
proc newSdramHandle(): SdramHandle {.importcpp: "SdramHandle()", constructor,
                                     header: "dev/sdram.h",
                                     header: "dev/sdram.h", ...raises: [],
                                     tags: [], forbids: [].}
Source   Edit  
proc samplesToMegabytes(samples: int; bytesPerSample: int = 4): float {.
    header: "dev/sdram.h", ...raises: [], tags: [], forbids: [].}
Convert number of samples to megabytes (assumes float32 by default) Example: let mb = samplesToMegabytes(1000000) Source   Edit  

Templates

template sdramArray(T: typedesc; size: int): untyped {..}
Create an array in SDRAM BSS section Example: var buffer = sdramArray(float32, 100000) Source   Edit