Phase Distortion Oscillator


The basic theory behind a Phase Distortion Synthesis is described at It involves running the ordinary linear sinusoidal phase through a distortion function to produce near-arbitrary waveforms.

Octave Model

GNU Octave (available from is an open-source mathemtical modeling language that's compatible with the commercial Matlab package.

Download the model here:

The Octave model for the PDO consists of the following files:

  • phase_distortion_map.m
  • phase_distortion_synth.m
  • octave_test_pdo.m

Running octave_test_pdo from the Octave command line should produce 2 output plots. The first, labelled "Phase Map", shows the phase distortion function graphically. The second plot shows 2 periods of the resulting waveform.

In addition to the raw PDO model, two test "tunes" are provided which demonstrate usage of the model: pdo_tune and pdo_better_tune. The first just uses the PDO to play a simple little song, and consists of the files:

  • octave_test_tune.m
  • pdo_tune.m

The second is more sophisticated (and may take a VERY long time to run! you have been warned):

  • octave_test_better_tune.m
  • bad_lpf.m
  • pdo_better_tune.m
  • phase_distortion_slide.m
  • violin_synth.m

All three of the octave_test_* programs output a .raw file. This file can be opened in your audio editor of choice, the format is 48000 Hz Signed 16-bit Mono PCM.

VHDL Model (for Altera FPGAs)

Download VHDL Model v2 here:

The VHDL model here targets the Altera CycloneII EP2C35F672C6 device (as found on Altera DE2 boards).

File manifest:

  • phase_distortion_oscillator.qpf (Project File)
  • phase_distortion_oscillator.qsf (Settings File)
  • phase_distortion_oscillator.qws (Workspace File)
  • phase_distortion_oscillator.vwf (Waveform file)

Project files for the freely downloadable Quartus II Web Edition v6.0 Build 202. This software can be used to run software simulations of the oscillator, sample waveform stimulus files are provided in the package.

  • sin_rom.vhd
  • sin_table.mif

Altera altsyncram mega function instantiation of a single port 12000 word by 16 bit ROM. If porting to another architecture, this needs to be replaced with a ROM model that has 1 cycle of read latency!

  • constants.vhd
  • qlut_arbiter.vhd

Arbiter for the quarter-wave ROM above, allowing it to be shared among multiple oscillator instances. Example presented here is a 4-port arbiter, but it's very easy to make it as large as necessary. Up to 11 has been tested, and still synthesized to over 100 Mhz on the Cyclone II.

  • full_sin_lut.vhd

Quarter-wave symmetry mapping function. Creates a virtual 48000 word single-cycle Sin LUT from the 12000 word quarter-wave ROM.

  • mults18x18.vhd
  • mults_arbiter.vhd

Altera lpm_mult mega function instantiation of an 18x18 bit signed multiplier. A sample 4-port arbiter for the multiplier is provided.

  • phase_generator.vhd

Flexible, reusable linear phase generator block with synchronous load.

  • phase_distorter.vhd
  • pdo.vhd

The phase distorter implements the FSM to compute the distortion function. PDO is the oscillator top-level.

  • sinosc_fsm.vhd
  • sinosc.vhd

Sinusoidal oscillator that shares a common sin LUT arbiter interface. Purely combinational, no multipliers are required by this block.

  • test_toplevel.vhd

Example top-level with 4 PDO instances. This top-level models 4 phase distortion oscillators (with built-in output gain) using only 1 signed 18x18 multiplier.

Instance 1 and 2 output a "Sin Square" shape at 100hz and 200hz respectivelly, while instances 3 and 4 will output a "Sin Pulse" (see "Waveform Presets" below) wave at 400hz and 800hz.

A sample instantiation of the sinusoidal oscillator is provided, but the SIN arbiter will need to be extended with more ports if you want to actually hook it up.

In theory, 140 PDO instances (with output gain) could fit into a CycloneII EP2C35F672C6 device; however scaling issues with the Sin LUT would likely be encountered. Possible solutions are 1) use a dual-port ROM, 2) use 2 full-sized ROMs (the FPGA has enough memory bits for it), or 3) half the size of the ROM (use linear interpolation in full_sin_lut.vhd to re-consutrct) and use 4 of the resulting ROMs. If #1 and #3 are combined you should be able to get 8 effective Sin ROMs.

VHDL Model Interfaces

Clock and control:

  • reset - Active-high reset
  • sysclk - System Clock, should be at least 20-30x sampclk.
  • sampclk - Sample "Clock". This is actually a signal syncronous to sysclk. A rising edge on this signal triggers the PDO to generate a new sample. It will respond by dropping audio_valid (see below), and eventually re-asserting audio_valid when the computation of the sample is done. These two can be considered a sample-request and sample-acknowledge pair, and a DAC FIFO controller can be built using them. With all that being said, Sampclk should run at 48 khz !!!! . Any other sample rate will require major architectural changes (in size of the Sin LUT, and inside the PDO).

PDO parameters:

  • f - Integer frequency of the oscillator
  • k - Distortion function kneepoints. This is an array of 9 16-bit values (n==0..8) that represent the points d[6000*n] of the distortion function. The PDO works by taking the phase coming from the phase generator, and mapping it through this function to get 'distorted' phase. This is then either output directly, or sent to the sin LUT (see below).
  • sinlut_en - If true, the distorted phase will be sent through the Sin LUT. This means the valid range of the kneepoints is only 0 - 48000! This allows the oscillator to make sinusoidal waveforms. If false, the distorted phase will be output directly, and the valid range of the kneepoints is 0 - 65535. This allows the oscillator to make linear waveforms (like some really killer saws!)
  • output_gain - An 8-bit unsigned value specifying the output gain. 0x80 represents unity gain. Lower values will attenuate the output. Higher values aren't recommended although values slightly higher, up to about 0x84, will still be OK; the sin LUT does not store a full-amplitude sin wave, there's a small ceiling.

Phase generator control:

  • phase - Phase override value 0-48000 (in steps of 2pi/48000)
  • phase_sync - When true, PDO phase will lock to the value of the phase input above and will not increment the next rising edge of sampclk.

Sin LUT arbiter signals:

  • qlut_addr - Requested address (0-11999)
  • qlut_read_req, qlut_data_valid - Assert qlut_read_req and hold it high for the duration of the read request. When the read is granted, qlut_data_valid will pulse high for 1 cycle and qlut_read_req should be deasserted.
  • qlut_data - LUT data (must be registered elsewhere! Only valid for single cycle when qlut_data_valid is high).

Multiplier arbiter signals:

  • mult_a, mult_b - 18-bit signed A and B operands.
  • mult_req, mult_valid - Identical protocol to Sin LUT above.
  • mult_p - 36-bit signed product.

Audio output bus:

  • audio_out - Mono, Signed 16-bit PCM Output
  • audio_valid - True when the current audio_out value is "valid" for the current sample. A low to high transition on sampclk will cause this to go False for some time (while the PDO computes a new sample), and then eventually become True again when the data is ready. Data will be held valid until the next sampclk rising edge.

Waveform Presets

Here are some interesting waveform presets. The first 9 values are the kneepoint constants, and the last value is the sin lut enable bit.

  • Pure Sine {0, 6001, 12001, 18001, 24001, 30001, 36001, 42001, 48001}, 1
  • Double Sine {0, 12001, 24001, 36001, 48001, 36001, 24001, 12001, 1}, 1
  • Sine Saw {0, 3001, 7546, 20814, 27938, 38664, 42834, 45564, 48001}, 1
  • Sine Square1 {36000, 12000, 12000, 12000, 12000, 36000, 36000, 36000, 36000}, 1
  • Sine Square2 {0, 12000, 12000, 12000, 12000, 0, 0, 0, 0}, 1
  • Sine Pulse {36000, 18000, 12000, 12000, 18000, 24000, 30000, 36000, 36000}, 1
  • Sine Reznatr {0, 36721, 961, 18001, 24001, 30001, 36001, 42001, 48001}, 1
  • Lin Ramp1 {0, 8192, 16384, 24576, 32768, 40960, 49152, 57344, 64000}, 0
  • Lin Ramp2 {32768, 36864, 40960, 45056, 49152, 53248, 57344, 61440, 64000}, 0
  • Lin Ramp3 {32768, 36865, 40960, 45056, 49152, 53248, 57344, 61440, 32768}, 0
  • Lin Saw1 {0, 16384, 32768, 49152, 64000, 49152, 32768, 16384, 0}, 0
  • Lin Saw2 {32768, 40960, 49152, 57344, 64000, 57344, 49152, 40960, 32768}, 0