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:
- Dumped the time-domain data to a text file on disk (in Cadence/Skill)
- Read the time-domain data using Matlab
- Perform the FFT (in Matlab)
- Write the frequency-domain data to a text file (in Matlab)
- Read the frequency-domain data (in Cadence/Skill)
- 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:
1 2 3 4 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 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.
1 2 3 4 5 6 7 8 9 |
; 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
1 2 |
lsfqueue = nil jobname = "reg0650_tran_paw_baseline" |
In case we want to use LSF for load-balancing
1 2 |
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.
1 2 |
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).
1 2 3 4 5 6 |
;;;;;;;;; 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).
1 2 3 4 5 6 7 |
;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.
1 2 3 4 5 6 7 8 |
; 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
; 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):
1 2 3 4 5 6 7 |
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:
1 2 3 4 5 6 |
; 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:
1 2 |
simulator( 'UltraSim ) resultsDir( strcat( wad "/simulation/" library "/" cell "/" view "/" simulator() ) ) |
In case we want to use LSF for load-sharing:
1 2 3 4 5 |
if(lsfqueue then hostMode( 'distributed ) else hostMode( 'local ) ) |
Define the test bench cell to be simulated (in read-only mode):
1 |
design( library cell view "r" ) |
Define model files. Note that the proc,cap, etc. Skill variables are used:
1 2 3 4 5 6 7 8 9 |
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:
1 2 3 |
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
1 2 3 4 5 6 |
desVar( "fR" fR ) desVar( "vdd" 2.5 ) desVar( "vidc" vidc ) desVar( "inputmag" inputmag ) desVar( "fin" fin ) desVar( "fQ" "36*fR" ) |
UltraSim simulation options:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
; 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:
1 2 3 4 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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 🙂
1 |
; vim: ft=ocean |
ReadPWM
This little matlab function reads the text file and assigns variables in Matlab:
1 2 3 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
% 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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.
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
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.
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
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.