Non-Radix-2 FFT in Cadence/Ocean/Skill/Spectre | Using Cadence IPC to talk to Matlab (or anything else)

Introduction

I had been working on a pulse-width-modulation (PWM) design. It was a pseudo-digital implementation, in that the output was clocked by a high-speed clock. The actual switching rate was much lower than this clock. I wanted to simulate this design in Cadence/Spectre by running a transient and then taking an FFT.

However, I ran into a bit of a snag: the quantization clock and my input clock rates were defined by the design, and they weren’t up to me. Moreover, these weren’t related by a power of 2. Unfortunately, Cadence Ocean (Skill) commands only allow for a radix-2 (power of 2 length) FFT. So, I was stuck. I needed a way to do a non-radix-2 FFT in Cadence. Here’s how I solved it using Matlab and getting Cadence and Matlab to talk (in a limited fashion).

What I ended up doing was writing some Skill IPC (inter-process communication) code that:

  1. Dumped the time-domain data to a text file on disk (in Cadence/Skill)
  2. Read the time-domain data using Matlab
  3. Perform the FFT (in Matlab)
  4. Write the frequency-domain data to a text file (in Matlab)
  5. Read the frequency-domain data (in Cadence/Skill)
  6. Plot FFT and compute SNR/SNDR (in Cadence/Skill)

The scripts

These files are copyright Poojan Wagh, 2009. They are released under the GPL. See http://www.gnu.org/licenses/gpl.txt.

These files come with absolutely no warranty, expressed or implied. Use at your own risk. If these scripts destroy your entire design database, that’s your fault for using them.

The Ocean Script

Here’s the ocean script I used. It’s called text.txt, because it is actually contained as a cell-view (text file type). Here’s a run-through of the code:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; copy this into CIW:
; runscript( “pwm3_sim” “reg0650_tran_paw” “ocean_ultrasim_getsndr_baseline” )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

I’ve defined runscript() as a function which loads and executes a text-file cell-view.

; Copyright (c) 2009 Poojan Wagh
; Released under GPL (http://www.gnu.org/licenses/gpl.txt)
; $Revision: 1511 $
library = “pwm3_sim”
cell = “reg0650_tran_paw”
view = “config_baseline”
proc= “typ”
cap = “typ”
res = “typ”

library, cell, view, proc (typ/wcs/bcs), etc. are all defined as skill variables for easier changing & for future corner simulation

lsfqueue =  nil
jobname = “reg0650_tran_paw_baseline”

In case we want to use LSF for load-balancing

wad = getShellEnvVar(”WORKAREA_DIR”)
dumpfile = strcat(wad “/matfft/reg0650_tran_paw_baseline.pwm”)

WORKAREA_DIR is the area where cadence is started. The PWM dump-out file will be in a mattfft subdirectory of it.

runit = t;
plotpsd = t;

runit determines whether we run the simulation or just plot the FFT/SNR/SNDR. plotpsd determines if we plot the power spectral density (PSD) or not (mostly for debugging).

;;;;;;;;;  Max Amplitude
inputmag =1.8;
vidc = 0.0;
fR = 460k;
ncs = 36;
fQ = ncs*fR;

inputmag is how high of an input signal we are sending to the PWM modulator. fR is the switching rate. fQ is the sampling rate. Note that fR=36*fQ, so this we don’t want a radix-2 FFT. Doing a radix-2 FFT would result in spectral leakage (and corrupt our SNR measurement).

;Changes 1024 pts **************************************
ncyc = 1024;
kcyc = 512;
simtime = (ncyc+kcyc)/fR
fbin = fR/ncyc;
fin = 13*fbin;
bw = round(20E3/fbin)*fbin;

I run the simulation for a total of $$\over{ncyc+kcyc}{fR}$$ cycles of fR. However, I throw away the first kcyc cycles to allow for startup transients.

; procedures used in this script:
; write a command to matlab
procedure( writemat(mipc cmd )
  ipcWaitForProcess(mipc)
  ipcWriteProcess(mipc sprintf(nil “disp ‘Executing %s’\n” cmd));
  ipcWaitForProcess(mipc)
  ipcWriteProcess(mipc sprintf(nil “%s\n” cmd));
  )

This (above) is a little wrapper skill function that sends a command to Matlab. I made it so that it also displays the command being executed in Cadence (for debugging).

The following function computes the PSD by sending commands to Matlab

; compute psd from matlab
procedure( matpsd( dumpfile fR ncs ncyc)
  let( (fin psdfile fswfile freqvec psdvec persist matlog)
    printf(”Starting matlab to compute PSD\n”)
    sprintf(matlog “%s.log” dumpfile)
    printf(”Matlab log file: %s\n” matlog)
    workareadir = getShellEnvVar(”WORKAREA_DIR”)
    mipc = ipcBeginProcess(strcat(”cd ” workareadir “/matfft && matlab -nodisplay -logfile ” matlog))
    writemat(mipc “clear all”);
    writemat(mipc “global ncs dt fsw”)
    writemat(mipc sprintf(nil “fsw = %g” fR));
    writemat(mipc sprintf(nil “ncs = %u” ncs));
    writemat(mipc sprintf(nil “ncyc = %u” ncyc));
    writemat(mipc “dt = 1/(fsw*ncs)”);
    writemat(mipc sprintf(nil “[yout, xin] = readpwm(’%s’);” dumpfile))
    writemat(mipc “[Py, freq] = getpsd(sign(yout), ncs*fsw, ncs*ncyc);”);
    sprintf(psdfile “%s.psd” dumpfile);
    writemat(mipc sprintf(nil “fout = fopen(’%s’, ‘w’)” psdfile))
    writemat(mipc “fprintf(fout, ‘%.12g %.12g\\n’, [freq(:) Py(:)]‘)”)
    writemat(mipc “fclose(fout)”)
    writemat(mipc “exit”);
    printf(”Waiting for Matlab to finish”);
    ; wait for matlab to finish:
    while( ipcIsAliveProcess(mipc)
      printf(”.”)
      ipcSleep(1)
    )
    fin = infile(psdfile);
    freqvec = nil;
    psdvec = nil
    printf(”Reading results from Matlab\n”);
    while( fscanf(fin “%f %f” frq psd)
      if(!freqvec then
        freqvec = drCreateVec(’double list(frq))
      else
        drAddElem(freqvec frq);
      )
      if(!psdvec then
        psdvec = drCreateVec(’double list(psd))
      else
        drAddElem(psdvec psd)
      )
    )
    close(fin);
    psdpwm = drCreateWaveform(freqvec psdvec)
  ) ; let
  psdpwm        ; return psdpwm
) ; procedure

You’ll notice that the skill variables are actually sent to matlab, not hard-coded anywhere. So, changing the variable assignments in the skill/ocean file will be reflected in Matlab. Several Matlab helper functions are used:

  • readpwm reads the text file
  • getpsd computes the psd
    • bh4 defines a minimum 4-term blackman-harris window; when I wrote this script, it was not available in Matlab

Each of these Matlab .m files are included in the attached tarball. In addition, I define a few Ocean/Skill helper functions:

sumpsd sums up spectral power over a frequency range (fstart to fend):

procedure( sumpsd( psd fstart fend)
  if(fstart < fend then
    integ( clip(psd fstart fend) )
  else
    0.0
  )
)

getpow gets the spectral power of a tone centered at fc and takes into account spectral growth around fc by speclobew bins of the FFT:

; get power of signal centered at fc
procedure( getpow( psd fc fbin )
  let( list( (speclobew 5) )
    sumpsd(psd fc-speclobew*fbin fc+speclobew*fbin)
  )
)

Define simulator, output directory, etc:

simulator( ‘UltraSim )
resultsDir( strcat( wad “/simulation/” library “/” cell “/” view “/” simulator() ) )

In case we want to use LSF for load-sharing:

if(lsfqueue then
  hostMode( ‘distributed )
else
  hostMode( ‘local )
  )

Define the test bench cell to be simulated (in read-only mode):

design(	library cell view “r” )

Define model files. Note that the proc,cap, etc. Skill variables are used:

printf(”$Revision: 1511 $ Using rev1e model files\n”)
modelFile(
    list(”/pdk_dir/amsmodels/spectre/rev1e.scs” “base”)
    list(”/pdk_dir/amsmodels/spectre/rev1e.scs” strcat(proc “_fet”))
    list(”/pdk_dir/amsmodels/spectre/rev1e.scs” strcat(proc “_bjt”))
    list(”/pdk_dir/amsmodels/spectre/rev1e.scs” strcat(cap  “_capacitor”))
    list(”/pdk_dir/amsmodels/spectre/rev1e.scs” strcat(res  “_resistor”))
    list(”/pdk_dir/amsmodels/spectre/rev1e.scs” strcat(proc “_diode”))
)

Define the simulation time and analysis options:

sprintf(sstop, “%.12g”, simtime);
analysis(’tran ?stop sstop  ?saveOP t  ?write “spectre.ic”
		?writefinal “spectre.fc”  ?method “gear2″  ?strobeperiod “1.0/fQ”  ?maxstepU “1.0/(16*fQ)”  )

Define design variables. Note that these are assigned from the Skill variables, so changing just the skill variables at the top of the file propage down through the design netlist and into matlab

desVar(	  “fR” fR	)
desVar(	  “vdd” 2.5	)
desVar(	  “vidc” vidc	)
desVar(	  “inputmag” inputmag	)
desVar(	  “fin” fin	)
desVar(	  “fQ” “36*fR”	)

UltraSim simulation options:

option(	‘tol  “0.001″
	‘dc  “spectre DC (3)”
	’scale  “”
	‘wf_format  “PSF”
	‘pn_method  “keep”
	‘analog  “off”
	’speed  “Extreme Accuracy (1)”
	’sim_mode  “Analog (A)”
)

saveOption(’UsimOptionDepth “20″)
saveOption(’UsimOptionAllAnalogNV t)
saveOption(’UsimOptionProbeAnalog t)
temp( 27 )

Actually run the simulation:

; either this:
if(runit then
  startrun = getCurrentTime()
  if(lsfqueue then
    run( ?queue lsfqueue ?block t ?jobName jobname)
  else
    run()
  )
  endrun = getCurrentTime()
  runtime = compareTime(endrun, startrun);
  printf(”Run took %d hours %d minutes %d seconds\n” floor(runtime/60/60) mod(floor(runtime/60), 60) mod(runtime, 60));
else
  openResults(resultsDir());
)
selectResults( ‘tran )

Dump out the time-domain file:

if(dumpfile then
  printf(”dumping to file %s\n” dumpfile)
  ocnPrint(v(”/PWM_P”)-v(”/PWM_M”) ?output dumpfile ?precision 12 ?numberNotation `none)
)

Compute the PSD through Matlab

if(plotpsd then
  psdpwm = matpsd( dumpfile fR ncs ncyc )
  ; PSig = sumpsd(psdpwm fin-5*fbin fin+5*fbin)     ; Signal Power
  PSig = getpow(psdpwm fin fbin)                                        ; Signal Power
  PND = sumpsd(psdpwm 0 min(bw fin-6*fbin)) + sumpsd(psdpwm fin+6*fbin bw) ; Noise Power
  PN = PND;
  exbw = 0;
  ; for SNR measurement, exclude odd-mode distortion
  ; from 3*fin to bw
  for( k 1 floor(bw/2/fin)
    fex1 = (2*k+1)*fin-5*fbin
    fex2 = min(bw (2*k+1)*fin+5*fbin)
    psdex = sumpsd(psdpwm fex1 fex2);
    ; printf(”k = %u Excluding distortion from %f kHz to %f kHz (%g – %g)\n” 2*k+1 fex1 fex2 PN psdex)
    PN = PN – psdex;
    exbw = exbw + (fex2-fex1);
    ; printf(”Excluding distortion from %f kHz to %f kHz (%f kHz)\n” fex1/1.0k fex2/1.0k (fex2-fex1)/1.0k);
  )
  PN = PN / (1 – exbw/bw)           ;compensate for frequencies we have excluded
                                    ;(assuming white noise)
  SNDR = PSig/PND;
  SNR = PSig/PN;
  SNDRdB = db10(SNDR);
  SNRdB = db10(SNR);
  printf(”SNDR [%s/%s/%s]= %.2f dB\n” library cell view SNDRdB);
  printf(”SNR [%s/%s/%s]= %.2f dB\n” library cell view SNRdB);
  sprintf(wintitle “PSD of Digitized PWM [%s/%s/%s] SNDR = %.0f dB NSR = %.0f dB” library cell view SNDRdB SNRdB)
  plot(db10(clip(psdpwm, fin-5*fbin, fin+5*fbin)) db10(psdpwm) ?expr list(”Desired Signal” wintitle))
  xLimit(list(0 bw))
)

I use vim :)

; vim: ft=ocean

ReadPWM

This little matlab function reads the text file and assigns variables in Matlab:

function [yout, xin] = readpwm(filename)
  [time, pwm, xin] = textread(filename, ‘%f %f %f’, -1, ‘headerlines’, 5);
  yout = pwm;

GetPSD

This matlab function gets the PSD from the time-domain data:

% function [ypsd, freq] = getpsd(x, fs, [psdlen])
%   return psd ypsd and frequency scaling of signal x
%   with sample rate fs and psd length psdlen.
%   copied from getpsd.m
function [ypsd, freq] = getpsd(x, fs, psdlen)
  x = x(:)’;
  if(nargin > 2)
    if(psdlen < length(x))
      y = x(1:psdlen).*bh4(psdlen);
    else
      y = x .* bh4(length(x));
    end
  end
  ypsd = abs(psd(y, psdlen)).^2;
  freq = (0:psdlen-1)*fs/psdlen;

Note that it also does the limiting function (using only the last psdlen values in the time-domain data).

BH4

Minimum 4-term blackman-harris window:

function [bh] = bh4(N)
% w = bh4(N)
%
%    Generates Blackman-Harris window of length N
%
a0=0.35875;
a1=0.48829;
a2=0.14128;
a3=0.01168;
theta = (0:N-1)*2*pi/N;
bh=a0-a1*cos(theta)+a2*cos(2*theta)-a3*cos(3*theta);

The Tarball

All the above scripts are contained matfft.

IPC

All of the above was done using Cadence IPC. This is basically a set of functions that let you remote-control another command. The standard input/output (stdin/stdout) of the child process is connected to the Cadence instance. Using ipcReadProcess in Cadence reads the child’s stdout. Using ipcWriteProcess writes to the child’s stdout. In theory, one could run any Matlab program from Cadence using this method.

However, one could read/write to any text-mode program using this method. For a while, I thought about using FFTW and coding a small C program to compute the above FFT’s. The same C program could be controlled using IPC. However, I wasn’t getting paid to program (at the time), and I already had most of the functions already in Matlab.

This entry was posted in Analog Professional, Digital Professional and tagged , , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

4 Comments

  1. Joe Golat
    Posted September 2, 2009 at 10:01 am | Permalink

    Hey Poojan,

    I agree with Mark. I can’t find the definition of matpsd in your code. I see the function getpsd. However, getpsd is passed 3 variables and matpsd is passed 4.

    Joe

    • Posted September 2, 2009 at 10:35 pm | Permalink

      Hi, there Go-Go Joe-Joe! (or was it Joe-Joe Go-Go?)

      Look up!

      I don’t know what version of the tarball I threw into the post. You guys are right. Regardless, the code for matpsd is in the post.

  2. Mark Lehne
    Posted April 10, 2009 at 5:38 pm | Permalink

    I was trying to use your Non-Radix-2 FFT MATLAB files, but noticed that your tarball does not include ‘matpsd’. I assume this is the file where you compute the FFT. Can you please email it to me?

    I write many scripts in Ocean, for mixed signal circuits, and I think your tool would be useful. Thanks for taking the time to post it.

    Thanks,
    Mark Lehne

    • Posted April 10, 2009 at 10:33 pm | Permalink

      Hi, Mark.

      The matpsd function is present in the matfft/text.txt file. In fact, several functions are defined in this file; it’s sort of important.

      Let me know if you have any further problems. Take care.

Post a Comment

You must be logged in to post a comment.