{"id":648,"date":"2009-01-19T09:43:53","date_gmt":"2009-01-19T14:43:53","guid":{"rendered":"https:\/\/www.circuitdesign.info\/blog\/2009\/01\/non-radix-2-fft-in-cadenceoceanskillspectre-using-cadenc-ipc-to-talk-to-matlab-or-anything-else\/"},"modified":"2009-01-20T14:49:29","modified_gmt":"2009-01-20T19:49:29","slug":"non-radix-2-fft-in-cadenceoceanskillspectre-using-cadenc-ipc-to-talk-to-matlab-or-anything-else","status":"publish","type":"post","link":"https:\/\/www.circuitdesign.info\/blog\/2009\/01\/non-radix-2-fft-in-cadenceoceanskillspectre-using-cadenc-ipc-to-talk-to-matlab-or-anything-else\/","title":{"rendered":"Non-Radix-2 FFT in Cadence\/Ocean\/Skill\/Spectre | Using Cadence IPC to talk to Matlab (or anything else)"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>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.<\/p>\n<p>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&#8217;t up to me. Moreover, these weren&#8217;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&#8217;s how I solved it using Matlab and getting Cadence and Matlab to talk (in a limited fashion).<\/p>\n<p><!--more-->What I ended up doing was writing some Skill IPC (inter-process communication) code that:<\/p>\n<ol>\n<li>Dumped the time-domain data to a text file on disk (in Cadence\/Skill)<\/li>\n<li>Read the time-domain data using Matlab<\/li>\n<li>Perform the FFT (in Matlab)<\/li>\n<li>Write the frequency-domain data to a text file (in Matlab)<\/li>\n<li>Read the frequency-domain data (in Cadence\/Skill)<\/li>\n<li>Plot FFT and compute SNR\/SNDR (in Cadence\/Skill)<\/li>\n<\/ol>\n<h2>The scripts<\/h2>\n<p>These files are copyright Poojan Wagh, 2009. They are released under the GPL. See http:\/\/www.gnu.org\/licenses\/gpl.txt.<\/p>\n<p>These files come with absolutely no warranty, expressed or implied. Use at your own risk. If these scripts destroy your entire design database, that&#8217;s your fault for using them.<\/p>\n<h3>The Ocean Script<\/h3>\n<p>Here&#8217;s the ocean script I used. It&#8217;s called text.txt, because it is actually contained as a cell-view (text file type). Here&#8217;s a run-through of the code:<\/p>\n<pre>;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\r\n; copy this into CIW:\r\n; runscript( \"pwm3_sim\" \"reg0650_tran_paw\" \"ocean_ultrasim_getsndr_baseline\" )\r\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<\/pre>\n<p>I&#8217;ve defined runscript() as a function which loads and executes a text-file cell-view.<\/p>\n<pre>; Copyright (c) 2009 Poojan Wagh\r\n; Released under GPL (http:\/\/www.gnu.org\/licenses\/gpl.txt)\r\n; $Revision: 1511 $\r\nlibrary = \"pwm3_sim\"\r\ncell = \"reg0650_tran_paw\"\r\nview = \"config_baseline\"\r\nproc= \"typ\"\r\ncap = \"typ\"\r\nres = \"typ\"<\/pre>\n<p>library, cell, view, proc (typ\/wcs\/bcs), etc. are all defined as skill variables for easier changing &amp; for future corner simulation<\/p>\n<pre>lsfqueue =  nil\r\njobname = \"reg0650_tran_paw_baseline\"<\/pre>\n<p>In case we want to use LSF for load-balancing<\/p>\n<pre>wad = getShellEnvVar(\"WORKAREA_DIR\")\r\ndumpfile = strcat(wad \"\/matfft\/reg0650_tran_paw_baseline.pwm\")<\/pre>\n<p>WORKAREA_DIR is the area where cadence is started. The PWM dump-out file will be in a <code>mattfft<\/code> subdirectory of it.<\/p>\n<pre>runit = t;\r\nplotpsd = t;<\/pre>\n<p>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).<\/p>\n<pre>;;;;;;;;;  Max Amplitude\r\ninputmag =1.8;\r\nvidc = 0.0;\r\nfR = 460k;\r\nncs = 36;\r\nfQ = ncs*fR;<\/pre>\n<p>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&#8217;t want a radix-2 FFT. Doing a radix-2 FFT would result in spectral leakage (and corrupt our SNR measurement).<\/p>\n<pre>;Changes 1024 pts **************************************\r\nncyc = 1024;\r\nkcyc = 512;\r\nsimtime = (ncyc+kcyc)\/fR\r\nfbin = fR\/ncyc;\r\nfin = 13*fbin;\r\nbw = round(20E3\/fbin)*fbin;<\/pre>\n<p>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.<\/p>\n<pre>; procedures used in this script:\r\n; write a command to matlab\r\nprocedure( writemat(mipc cmd )\r\n  ipcWaitForProcess(mipc)\r\n  ipcWriteProcess(mipc sprintf(nil \"disp 'Executing %s'\\n\" cmd));\r\n  ipcWaitForProcess(mipc)\r\n  ipcWriteProcess(mipc sprintf(nil \"%s\\n\" cmd));\r\n  )<\/pre>\n<p>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).<\/p>\n<p>The following function computes the PSD by sending commands to Matlab<\/p>\n<pre>; compute psd from matlab\r\nprocedure( matpsd( dumpfile fR ncs ncyc)\r\n  let( (fin psdfile fswfile freqvec psdvec persist matlog)\r\n    printf(\"Starting matlab to compute PSD\\n\")\r\n    sprintf(matlog \"%s.log\" dumpfile)\r\n    printf(\"Matlab log file: %s\\n\" matlog)\r\n    workareadir = getShellEnvVar(\"WORKAREA_DIR\")\r\n    mipc = ipcBeginProcess(strcat(\"cd \" workareadir \"\/matfft &amp;&amp; matlab -nodisplay -logfile \" matlog))\r\n    writemat(mipc \"clear all\");\r\n    writemat(mipc \"global ncs dt fsw\")\r\n    writemat(mipc sprintf(nil \"fsw = %g\" fR));\r\n    writemat(mipc sprintf(nil \"ncs = %u\" ncs));\r\n    writemat(mipc sprintf(nil \"ncyc = %u\" ncyc));\r\n    writemat(mipc \"dt = 1\/(fsw*ncs)\");\r\n    writemat(mipc sprintf(nil \"[yout, xin] = readpwm('%s');\" dumpfile))\r\n    writemat(mipc \"[Py, freq] = getpsd(sign(yout), ncs*fsw, ncs*ncyc);\");\r\n    sprintf(psdfile \"%s.psd\" dumpfile);\r\n    writemat(mipc sprintf(nil \"fout = fopen('%s', 'w')\" psdfile))\r\n    writemat(mipc \"fprintf(fout, '%.12g %.12g\\\\n', [freq(:) Py(:)]')\")\r\n    writemat(mipc \"fclose(fout)\")\r\n    writemat(mipc \"exit\");\r\n    printf(\"Waiting for Matlab to finish\");\r\n    ; wait for matlab to finish:\r\n    while( ipcIsAliveProcess(mipc)\r\n      printf(\".\")\r\n      ipcSleep(1)\r\n    )\r\n    fin = infile(psdfile);\r\n    freqvec = nil;\r\n    psdvec = nil\r\n    printf(\"Reading results from Matlab\\n\");\r\n    while( fscanf(fin \"%f %f\" frq psd)\r\n      if(!freqvec then\r\n        freqvec = drCreateVec('double list(frq))\r\n      else\r\n        drAddElem(freqvec frq);\r\n      )\r\n      if(!psdvec then\r\n        psdvec = drCreateVec('double list(psd))\r\n      else\r\n        drAddElem(psdvec psd)\r\n      )\r\n    )\r\n    close(fin);\r\n    psdpwm = drCreateWaveform(freqvec psdvec)\r\n  ) ; let\r\n  psdpwm        ; return psdpwm\r\n) ; procedure<\/pre>\n<p>You&#8217;ll notice that the skill variables <em>are actually sent to matlab<\/em>, 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:<\/p>\n<ul>\n<li>readpwm reads the text file<\/li>\n<li>getpsd computes the psd\n<ul>\n<li>bh4 defines a minimum 4-term blackman-harris window; when I wrote this script, it was not available in Matlab<\/li>\n<li><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Each of these Matlab <code>.m<\/code> files are included in the attached tarball. In addition, I define a few Ocean\/Skill helper functions:<\/p>\n<p>sumpsd sums up spectral power over a frequency range (fstart to fend):<\/p>\n<pre>procedure( sumpsd( psd fstart fend)\r\n  if(fstart &lt; fend then\r\n    integ( clip(psd fstart fend) )\r\n  else\r\n    0.0\r\n  )\r\n)<\/pre>\n<p>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:<\/p>\n<pre>; get power of signal centered at fc\r\nprocedure( getpow( psd fc fbin )\r\n  let( list( (speclobew 5) )\r\n    sumpsd(psd fc-speclobew*fbin fc+speclobew*fbin)\r\n  )\r\n)<\/pre>\n<p>Define simulator, output directory, etc:<\/p>\n<pre>simulator( 'UltraSim )\r\nresultsDir( strcat( wad \"\/simulation\/\" library \"\/\" cell \"\/\" view \"\/\" simulator() ) )<\/pre>\n<p>In case we want to use LSF for load-sharing:<\/p>\n<pre>if(lsfqueue then\r\n  hostMode( 'distributed )\r\nelse\r\n  hostMode( 'local )\r\n  )<\/pre>\n<p>Define the test bench cell to be simulated (in read-only mode):<\/p>\n<pre>design(\tlibrary cell view \"r\" )<\/pre>\n<p>Define model files. Note that the <em>proc<\/em>,<em>cap<\/em>, etc. Skill variables are used:<\/p>\n<pre>printf(\"$Revision: 1511 $ Using rev1e model files\\n\")\r\nmodelFile(\r\n    list(\"\/pdk_dir\/amsmodels\/spectre\/rev1e.scs\" \"base\")\r\n    list(\"\/pdk_dir\/amsmodels\/spectre\/rev1e.scs\" strcat(proc \"_fet\"))\r\n    list(\"\/pdk_dir\/amsmodels\/spectre\/rev1e.scs\" strcat(proc \"_bjt\"))\r\n    list(\"\/pdk_dir\/amsmodels\/spectre\/rev1e.scs\" strcat(cap  \"_capacitor\"))\r\n    list(\"\/pdk_dir\/amsmodels\/spectre\/rev1e.scs\" strcat(res  \"_resistor\"))\r\n    list(\"\/pdk_dir\/amsmodels\/spectre\/rev1e.scs\" strcat(proc \"_diode\"))\r\n)<\/pre>\n<p>Define the simulation time and analysis options:<\/p>\n<pre>sprintf(sstop, \"%.12g\", simtime);\r\nanalysis('tran ?stop sstop  ?saveOP t  ?write \"spectre.ic\"\r\n\t\t?writefinal \"spectre.fc\"  ?method \"gear2\"  ?strobeperiod \"1.0\/fQ\"  ?maxstepU \"1.0\/(16*fQ)\"  )<\/pre>\n<p>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<\/p>\n<pre>desVar(\t  \"fR\" fR\t)\r\ndesVar(\t  \"vdd\" 2.5\t)\r\ndesVar(\t  \"vidc\" vidc\t)\r\ndesVar(\t  \"inputmag\" inputmag\t)\r\ndesVar(\t  \"fin\" fin\t)\r\ndesVar(\t  \"fQ\" \"36*fR\"\t)<\/pre>\n<p>UltraSim simulation options:<\/p>\n<pre>option(\t'tol  \"0.001\"\r\n\t'dc  \"spectre DC (3)\"\r\n\t'scale  \"\"\r\n\t'wf_format  \"PSF\"\r\n\t'pn_method  \"keep\"\r\n\t'analog  \"off\"\r\n\t'speed  \"Extreme Accuracy (1)\"\r\n\t'sim_mode  \"Analog (A)\"\r\n)\r\n\r\nsaveOption('UsimOptionDepth \"20\")\r\nsaveOption('UsimOptionAllAnalogNV t)\r\nsaveOption('UsimOptionProbeAnalog t)\r\ntemp( 27 )<\/pre>\n<p>Actually run the simulation:<\/p>\n<pre>; either this:\r\nif(runit then\r\n  startrun = getCurrentTime()\r\n  if(lsfqueue then\r\n    run( ?queue lsfqueue ?block t ?jobName jobname)\r\n  else\r\n    run()\r\n  )\r\n  endrun = getCurrentTime()\r\n  runtime = compareTime(endrun, startrun);\r\n  printf(\"Run took %d hours %d minutes %d seconds\\n\" floor(runtime\/60\/60) mod(floor(runtime\/60), 60) mod(runtime, 60));\r\nelse\r\n  openResults(resultsDir());\r\n)\r\nselectResults( 'tran )<\/pre>\n<p>Dump out the time-domain file:<\/p>\n<pre>if(dumpfile then\r\n  printf(\"dumping to file %s\\n\" dumpfile)\r\n  ocnPrint(v(\"\/PWM_P\")-v(\"\/PWM_M\") ?output dumpfile ?precision 12 ?numberNotation `none)\r\n)<\/pre>\n<p>Compute the PSD through Matlab<\/p>\n<pre>if(plotpsd then\r\n  psdpwm = matpsd( dumpfile fR ncs ncyc )\r\n  ; PSig = sumpsd(psdpwm fin-5*fbin fin+5*fbin)     ; Signal Power\r\n  PSig = getpow(psdpwm fin fbin)                                        ; Signal Power\r\n  PND = sumpsd(psdpwm 0 min(bw fin-6*fbin)) + sumpsd(psdpwm fin+6*fbin bw) ; Noise Power\r\n  PN = PND;\r\n  exbw = 0;\r\n  ; for SNR measurement, exclude odd-mode distortion\r\n  ; from 3*fin to bw\r\n  for( k 1 floor(bw\/2\/fin)\r\n    fex1 = (2*k+1)*fin-5*fbin\r\n    fex2 = min(bw (2*k+1)*fin+5*fbin)\r\n    psdex = sumpsd(psdpwm fex1 fex2);\r\n    ; printf(\"k = %u Excluding distortion from %f kHz to %f kHz (%g - %g)\\n\" 2*k+1 fex1 fex2 PN psdex)\r\n    PN = PN - psdex;\r\n    exbw = exbw + (fex2-fex1);\r\n    ; printf(\"Excluding distortion from %f kHz to %f kHz (%f kHz)\\n\" fex1\/1.0k fex2\/1.0k (fex2-fex1)\/1.0k);\r\n  )\r\n  PN = PN \/ (1 - exbw\/bw)           ;compensate for frequencies we have excluded\r\n                                    ;(assuming white noise)\r\n  SNDR = PSig\/PND;\r\n  SNR = PSig\/PN;\r\n  SNDRdB = db10(SNDR);\r\n  SNRdB = db10(SNR);\r\n  printf(\"SNDR [%s\/%s\/%s]= %.2f dB\\n\" library cell view SNDRdB);\r\n  printf(\"SNR [%s\/%s\/%s]= %.2f dB\\n\" library cell view SNRdB);\r\n  sprintf(wintitle \"PSD of Digitized PWM [%s\/%s\/%s] SNDR = %.0f dB NSR = %.0f dB\" library cell view SNDRdB SNRdB)\r\n  plot(db10(clip(psdpwm, fin-5*fbin, fin+5*fbin)) db10(psdpwm) ?expr list(\"Desired Signal\" wintitle))\r\n  xLimit(list(0 bw))\r\n)<\/pre>\n<p>I use <a title=\"VI Improved: my text editor of choice (but definitely not for everyone)\" href=\"http:\/\/www.vim.org\/\" target=\"_blank\">vim<\/a> \ud83d\ude42<\/p>\n<pre>; vim: ft=ocean<\/pre>\n<h3>ReadPWM<\/h3>\n<p>This little matlab function reads the text file and assigns variables in Matlab:<\/p>\n<pre>function [yout, xin] = readpwm(filename)\r\n  [time, pwm, xin] = textread(filename, '%f %f %f', -1, 'headerlines', 5);\r\n  yout = pwm;<\/pre>\n<h3>GetPSD<\/h3>\n<p>This matlab function gets the PSD from the time-domain data:<\/p>\n<pre>% function [ypsd, freq] = getpsd(x, fs, [psdlen])\r\n%   return psd ypsd and frequency scaling of signal x\r\n%   with sample rate fs and psd length psdlen.\r\n%   copied from getpsd.m\r\nfunction [ypsd, freq] = getpsd(x, fs, psdlen)\r\n  x = x(:)';\r\n  if(nargin &gt; 2)\r\n    if(psdlen &lt; length(x))\r\n      y = x(1:psdlen).*bh4(psdlen);\r\n    else\r\n      y = x .* bh4(length(x));\r\n    end\r\n  end\r\n  ypsd = abs(psd(y, psdlen)).^2;\r\n  freq = (0:psdlen-1)*fs\/psdlen;<\/pre>\n<p>Note that it also does the limiting function (using only the last <em>psdlen<\/em> values in the time-domain data).<\/p>\n<h3>BH4<\/h3>\n<p>Minimum 4-term blackman-harris window:<\/p>\n<pre>function [bh] = bh4(N)\r\n% w = bh4(N)\r\n%\r\n%    Generates Blackman-Harris window of length N\r\n%\r\na0=0.35875;\r\na1=0.48829;\r\na2=0.14128;\r\na3=0.01168;\r\ntheta = (0:N-1)*2*pi\/N;\r\nbh=a0-a1*cos(theta)+a2*cos(2*theta)-a3*cos(3*theta);<\/pre>\n<h3>The Tarball<\/h3>\n<p>All the above scripts are contained <a rel=\"attachment wp-att-649\" href=\"https:\/\/www.circuitdesign.info\/blog\/2009\/01\/non-radix-2-fft-in-cadenceoceanskillspectre-using-cadenc-ipc-to-talk-to-matlab-or-anything-else\/matfft\/\">matfft<\/a>.<\/p>\n<h2>IPC<\/h2>\n<p>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&#8217;s stdout. Using ipcWriteProcess writes to the child&#8217;s stdout. In theory, one could run any Matlab program from Cadence using this method.<\/p>\n<p>However, one could read\/write to <em>any<\/em> text-mode program using this method. For a while, I thought about using <a title=\"Open-source C FFT libraries\" href=\"http:\/\/www.fftw.org\/\" target=\"_blank\">FFTW<\/a> and coding a small C program to compute the above FFT&#8217;s. The same C program could be controlled using IPC. However, I wasn&#8217;t getting paid to program (<a title=\"I'm programming now, since I'm moving into software\" href=\"http:\/\/poojanblog.com\/blog\/2008\/11\/im-leaving-motorola\/\" target=\"_blank\">at the time<\/a>), and I already had most of the functions already in Matlab.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[3,107],"tags":[33,58,136,46,56,57],"class_list":["post-648","post","type-post","status-publish","format-standard","hentry","category-analog-pro","category-digital-professional","tag-cadence","tag-fft","tag-ipc","tag-matlab","tag-ocean","tag-skill"],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/poCEy-as","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/posts\/648","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/comments?post=648"}],"version-history":[{"count":8,"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/posts\/648\/revisions"}],"predecessor-version":[{"id":657,"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/posts\/648\/revisions\/657"}],"wp:attachment":[{"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/media?parent=648"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/categories?post=648"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.circuitdesign.info\/blog\/wp-json\/wp\/v2\/tags?post=648"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}