Optimize Transistor Sizes of Analog Circuit for Best Performance
This example shows you how to optimize transistor sizes of an analog circuit using Mixed-Signal Blockset™, Cadence Virtuoso ADE - MATLAB Integration Option, and Global Optimization Toolbox. This example shows you how to design an optimally sized single-stage CMOS operational transconductance amplifier (OTA). Transistor size optimization helps you save chip area while also meeting the given performance specifications. To run the optimization workflow, launch a MATLAB® session launched from Cadence® after running a nominal simulation. This example considers these OTA performance specifications:
bandwidth (3dB),
current,
gain,
slew rate, and
unity gain bandwidth
Click on "Open Live Script" to access the example files including the design database which is contained in OTAdesignDatabase.zip. The maestro view to be used in this example is under the hierarchy: msbOptim library, tb_opamp cell, maestro view. Follow the steps below in Cadence to setup the PDK and the required libraries:
Download the generic pdk "GPDK045" from the Cadence support page and place it in the same location as the project folder "msbOptim".
Edit the "cds.lib" file to update the library paths as needed.
Right-click on the test "JESD_RX_tb_opamp_1" in maestro view and set the path to "gpdk045.scs" under "Model Libraries".
The maestro view in the database under library "msbOptim" and cellname "tb_opamp" is already set up to run the optimization process. This includes setting up the design specifications in Outputs Setup of maestro view and declaring the device parameters to optimize.
Import adeInfo (Handle to Cadence Simulation Data)
Run a nominal simulation by disabling the Parameters and then launch MATLAB from the Results tab on maestro view. This makes the adeInfo object available in the workspace so you can also launch Cadence simulation from MATLAB.
Optimization Workflow
The first two steps in the workflow use Cadence Virtuoso while the third and fourth steps use MATLAB. The first two steps are already performed in the design provided with this example. You can skip to Start the optimization routine if you are using the design provided. But if you are using a different design for optimization, start from Setup optimization specifications.
Setup optimization specifications in the Outputs Setup of Cadence maestro view.
Declare optimization variables to be the transistor device parameters in the Cadence maestro view. Also define the range of values that the parameters can take.
Start the optimization routine which results in several simulation runs. Optimization routine stops when the goals or stop criteria are met.
Set the optimized parameter values to device parameters.
Setup Optimization Specifications
Define the performance specifications in the Outputs Setup of the maestro view. Then import them into MATLAB as optimization specifications.
To configure the specifications in the output table:
Add expressions to the output table for defining design specifications in maestro view. Currently only
>
and<
specifications are supported.Assign unique weights in the range (1,100) to the specifications with a weight of 100 being the highest priority spec.
Save the maestro view.
Declare Optimization Variables in Cadence
Optimization variables or parameters are the quantities that you vary to meet the design specifications. Follow the steps below to declare and setup device parameters as optimization variables. Setting up device parameters is explained in Virtuoso ADE Assembler User Guide under the section Working with Device Instance Parameters.
Add parameters to be optimized in the maestro view. This opens the schematic view which lets you set parameter range and also select the device parameters need to be matched. For example, select the input transistors of an opamp as matched parameters.
Set the range of values that each parameter can take in the format
Min:Step:Max
using the scientific or exponential notation. For example, if the parameter M24I can vary between 3 um and 10 um with a step size of 0.1 um, set the parameter as3e-6:0.1e-6:10e-6
. Similarly, if the parameter M24W can vary between 3 um and 40 um with a step size of 0.1 um, set the parameter as3e-6:0.1e-6:40e-6
.If you want to save the setup state to rerun the optimization process, click File > Save Setup State on the maestro view.
Select all the parameters, right click on the selection, and select Group as Parametric Set. Enable the Parameters and save the maestro view. More information about Parametric sets can be found in this Cadence blog post.
Start the Optimization Routine
Run the sections below in MATLAB to view and verify your settings from the Outputs Setup and Parameters from the maestro view.
msblks.internal.cadence2matlab.loadMaestroState(adeInfo.adeSession, "initialParamSetting", "All", "overwrite") % load the state saved in maestro view outputTable = getOutputTable(adeInfo)
[lower_bounds, upper_bounds, variableScaleFactor, variableNames] = findBoundsAndScale(adeInfo); paramTable = findOptimVariables(lower_bounds, upper_bounds, variableScaleFactor, variableNames)
You can run multiple Cadence simulations in parallel to reach the maximum number of simulations in fewer iterations. In this example, the maximum number of simulations is 32, and you can run 8 simulations in parallel. So it requires 4 iterations to run the complete optimization process.
Run the section below in MATLAB to setup optimization options.
numberParallelSims = 8; maxNumberSims = 32; % minWeight = min(outputTable.Weight); constraintTable = getConstraintTable(outputTable); minWeight = min(constraintTable.Weight); objectiveLimit = constraintTable.ObjConstr(constraintTable.Weight==minWeight)... .*constraintTable.PosNeg(constraintTable.Weight==minWeight); goalName = constraintTable.Name{constraintTable.Weight==minWeight}; if length(objectiveLimit) > 1 warning("Enter unique values for weights.") objectiveLimit = objectiveLimit(1); end intcon = 1:numel(upper_bounds); % array indicating all variables are integer valued % E.g., intcon = [1 2] indicates that first two variables are integers. plotHandle = @(x,y,z) customoptplot(x,y,z,goalName); maxTime = 20*60; %seconds options = optimoptions('surrogateopt','BatchUpdateInterval',numberParallelSims,'UseVectorized',true, ... 'MinSurrogatePoints',32,'ObjectiveLimit',objectiveLimit, 'PlotFcn', plotHandle,...%'surrogateoptplot', ... 'CheckpointFile','mycheckpoint.mat', 'MaxFunctionEvaluations', maxNumberSims); %'MaxTime', maxTime);
Run the following commands to start the optimization routine which kicks-off multiple simulations in Cadence.
useCheckPointFile = false; % Handle to objective function "getMetrics" objFunction = @(x) getMetrics(x,variableNames,variableScaleFactor,constraintTable); tic % Check if option for Checkpoint file if useCheckPointFile == false [sol,fval,eflag,output,trials] = surrogateopt(objFunction,lower_bounds,upper_bounds,intcon,options); else [sol,fval,eflag,output,trials] = surrogateopt('mycheckpoint',options); end toc
Run the following function calls to display the performance specifications and optimized transistor sizes after the optimization is complete.
% Final metrics and optimized parameters format shortE finalMetricTable = findFinalMetric(output, fval, constraintTable)
finalSolutionTable = findFinalSolution(variableNames, variableScaleFactor, sol)
Running Optimization from Saved Check Point
If the optimization process does not meet the design specifications, make the following changes in the section Start the Optimization Routine and rerun optimization:
Increase the maximum number of simulations to 64 (set the variable in the section Start the Optimization Routine to maxNumberSims to 64).
Load the saved maestro setup state by running the command below in MATLAB.
msblks.internal.cadence2matlab.loadMaestroState(adeInfo.adeSession, "initialParamSetting", "All", "overwrite") % load the state saved in maestro view
Set the variable useCheckPointFile to true and start the optimization so that the optimization starts from the last point it stopped.
Set Optimized Parameter Values
If you are satisfied with the performance specifications after optimization, run the command below in MATLAB to set the parameters to optimized values obtained from the simulation.
setFinalSolution((finalSolutionTable.Name)', num2cell(finalSolutionTable.Value'))
To backannotate the optimized parameters as design values in Cadence schematic, go to maestro view, select the run that meets all the specifications, e.g., Point 7 in this example, right click and select Backannotate.
Function Definitions
The objective and helper functions used in this example are defined here.
% Function definitions % Objective function is defined in this section function out = getMetrics(x,variableNames,varScaleFactor,constraintTable) % Obtain updated gain and current values % Inputs: Design variables % Outputs: Metrics after simulation adeInfoIn = evalin('base','adeInfo'); out.Fval = NaN(size(x,1),1); out.Ineq = NaN(size(x,1),height(constraintTable)-1); ii=1; for parameter=variableNames % adeSet(varName=char(variable), varValue=num2str(x(:,ii)'.*scaleFactor(ii)), adeInfo=adeInfo); adeSet(paramName=char(parameter), paramValue=num2str(x(:,ii)'.*varScaleFactor(ii)), adeInfo=adeInfoIn); ii = ii+1; end % Extract outputs from history feed them back to optim routine % Open history as adeInfo adeInfoSimNew = adeSim(adeInfo=adeInfoIn); adeInfoSimNew = adeInfoSimNew.loadResult; resultsTable = adeInfoSimNew.adeRDB.results; [fval, ineq] = getFvalIneq(size(x,1), resultsTable, constraintTable); % Output is a structure containing Fval (required) and Ineq (optional) out.Fval = fval; out.Ineq = ineq; end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Helper functions for optimization are defined next
function [lb, ub, scaleFactor, paramList] = findBoundsAndScale(adeInfoIn) % Find the bounds, scale factor and parameter list from the Cadence maestro % view adeSession = adeInfoIn.adeSession; parameters = msblks.internal.cadence2matlab.listMaestroParameters(adeSession); isParameter = 1; if numel(parameters) == 0 parameters = msblks.internal.cadence2matlab.listMaestroVariables(adeSession); isParameter = 0; end lb=zeros(1,numel(parameters)); ub=zeros(1,numel(parameters)); scaleFactor=zeros(1,numel(parameters)); ii=1; paramList = strings(1,size(parameters,2)); for param = parameters if isParameter paramValue = adeGet(adeInfo=adeInfoIn, paramName=param{1}); else paramValue = adeGet(adeInfo=adeInfoIn, varName=param{1}); end paramValue = str2num(paramValue); if ~isempty(paramValue) stepSize = paramValue(2) - paramValue(1); paramList(ii) = string(param{1}); lb(ii)=ceil(min(paramValue)/stepSize); % fix rounds towards 0 ub(ii)=ceil(max(paramValue)/stepSize); % fix rounds towards 0 scaleFactor(ii) = stepSize; ii=ii+1; end end lb=lb(1:ii-1); ub=ub(1:ii-1); scaleFactor=scaleFactor(1:ii-1); paramList(strcmp(paramList,""))=[]; % Make sure there is no value set to a matched parameter including "" end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function outputTable = getOutputTable(adeInfoIn) % Output setup returned as a table aie = msblks.internal.cadence2matlab.adeInfoExtender(adeInfoIn); signalTable = aie.outputTable; % Return only those rows that have a spec outputTable = signalTable(~cellfun(@isempty,signalTable.Spec),:);%signalTable(~isnan(signalTable.Weight),:); % Remove empty columns outputTable(:,all(ismissing(outputTable)))=[]; end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function optimizationVariables = findOptimVariables(lb, ub,variableScaleFactor, variableNames) % Scale the variables as needed and display the final values % sz = [numel(variableNames) 2]; % varTypes = {'string', 'double'}; % finalSolutionTable = table('Size',sz,'VariableTypes',varTypes); optimizationVariables = table(); optimizationVariables.Name = variableNames'; for ibb = 1:numel(ub) paramValues(ibb) = string([char(num2str(lb(ibb).*variableScaleFactor(ibb))),':',... char(num2str(variableScaleFactor(ibb))),':',char(num2str(ub(ibb).*variableScaleFactor(ibb)))]); % optimizationVariables.Value = (lb.*variableScaleFactor:variableScaleFactor:ub.*variableScaleFactor); end optimizationVariables.Value = paramValues'; end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function objConstrTable = getConstraintTable(constraintTable) % Add columns to constraint table with information on whether it has to be negative or positive, scaling constr = NaN(height(constraintTable),1); minmax = NaN(height(constraintTable),1); posneg = NaN(height(constraintTable),1); objConstrTable = constraintTable; objSpec = constraintTable.Spec; % make elements for two columns - constraint and minmax for ii=1:height(objConstrTable) t1 = objSpec(ii);%objConstrTable{ii,end}; constr(ii,1) = str2double(t1{1}(3:end)); minmax(ii,1) = t1{1}(1); if (char(minmax(ii,1)) == '>') || (string(char(minmax(ii,1))) == "maximize") posneg(ii,1) = -1; else posneg(ii,1) = 1; end end % add the columns %% objConstrTable.ObjConstr=constr; objConstrTable.Minmax=char(minmax); objConstrTable.PosNeg=posneg; % Find the scaling factor and scale the constraints objConstrTable.Scale = 10.^(-1*floor(log10(abs(objConstrTable.ObjConstr)))); objConstrTable.Weight = str2double(objConstrTable.Weight); % If any weight entry is empty, they show up as NaN after the above % command.Set such weights slightly above the minimum so that there % is only one minimum which solves a lot of issues in other functions minWeight = min(objConstrTable.Weight).*1.001; objConstrTable.Weight(isnan(objConstrTable.Weight)) = minWeight; objConstrTable.Scale = objConstrTable.Scale.*objConstrTable.PosNeg.*objConstrTable.Weight./100; objConstrTable.ScaledConstr = objConstrTable.Scale.*objConstrTable.ObjConstr; objConstrTable(:,all(ismissing(objConstrTable)))=[]; % Remove unwanted columns: objConstrTable.Type=[]; objConstrTable.EvalType=[]; objConstrTable.Output=[]; % objConstrTable.Save=[]; objConstrTable.Plot=[]; end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function [fval, ineq] = getFvalIneq(xHeight, resultsTable, constraintTable) % Get scaled constraints in the form of a matrix 'ineq' as required by optim function. % Goal is not scaled and is a column matrix 'fval' fval = NaN(xHeight, 1); ineq = NaN(xHeight,height(constraintTable)-1); % Goal is chosen as the metric that has a weight of 100 minWeight = min(constraintTable.Weight); goal = constraintTable.Name{constraintTable.Weight==minWeight}; iiq = 1; constrList = constraintTable.Name; for iic=1:numel(constrList) constr = constrList{iic}; metricVal = resultsTable{matches(resultsTable.Output, constr), 4}; % It is assumed that the scaling factor is in 2nd column from last if iscell(metricVal) % check if any cell is a logical 0. If it's a logical 0, assign a % value of 0 logicTest = cellfun(@(x) all(x),metricVal); if ~all(logicTest) [metricVal{~logicTest}] = deal(0); % deal is used to assign multiple values end metricVal = cell2mat(metricVal); end nMetricVal = numel(metricVal); % if number of elements in metricVal is lesser than % xHeight, add NaN at the end if nMetricVal < xHeight metricVal(nMetricVal+1:nMetricVal+xHeight-nMetricVal) = NaN; end metricScaled = metricVal.*constraintTable{matches(constraintTable.Name,constr),end-1}; % iic = iic+1; if string(constr) == string(goal) % do not scale the goal metric fval = constraintTable.PosNeg(iic)*metricVal; else ineq(:,iiq) = metricScaled - constraintTable.ScaledConstr(iic); iiq = iiq + 1; % index for ineq end end end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function finalMetricTable = findFinalMetric(optimOutput, fval, constraintTable) % solutionTable = constraintTable; minWeight = min(constraintTable.Weight); constraintsName = constraintTable.Name(~(constraintTable.Weight==minWeight)); scaledConstraints = constraintTable.ScaledConstr(~(constraintTable.Weight==minWeight)); constraintScale = constraintTable.Scale(~(constraintTable.Weight==minWeight)); finalMetricsAtStop = (optimOutput.ineq'+scaledConstraints)./constraintScale; % since goal is unscaled, grab it directly goal = constraintTable.Name{(constraintTable.Weight==minWeight)}; finalMetricsAtStop(end+1) = fval.*constraintTable.PosNeg((constraintTable.Weight==minWeight)); % put the goal at the end constraintsName{end+1} = goal; % Get the Spec information specs = constraintTable.Spec(~(constraintTable.Weight==minWeight)); specs{end+1} = constraintTable.Spec{constraintTable.Weight==minWeight}; % construct the final table finalMetricTable=table(); finalMetricTable.Name = constraintsName; % metricUnits = constraintTable.Units(~constraintTable.Weight==minWeight); finalMetricTable.FinalMetrics = finalMetricsAtStop; finalMetricTable.Specs = specs; % Units column if units are specified if any("Units" == string(constraintTable.Properties.VariableNames)) metricUnits = constraintTable.Units(~(constraintTable.Weight==minWeight)); metricUnits(end+1) = constraintTable.Units(constraintTable.Weight==minWeight); finalMetricTable.Units = metricUnits; end end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function finalSolutionTable = findFinalSolution(variableNames, variableScaleFactor, sol) % Scale the variables as needed and display the final values % sz = [numel(variableNames) 2]; % varTypes = {'string', 'double'}; % finalSolutionTable = table('Size',sz,'VariableTypes',varTypes); finalSolutionTable = table(); finalSolutionTable.Name = variableNames'; finalSolutionTable.Value = (variableScaleFactor.*sol)'; end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function setFinalSolution(names, values) % set parameters adeSet(paramName=names, paramValue=values); end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function stop = customoptplot(~,optimValues,state,optimGoal) persistent plotBest plotInitial plotBestInfeas plotInitialInfeas ... legendHndl legendStr legendHndlInfeas legendStrInfeas nFeas stop = false; if strcmpi(state,'init') plotBest = []; plotInitial = []; plotBestInfeas = []; plotInitialInfeas = []; legendHndl = []; legendStr = {}; legendHndlInfeas = []; legendStrInfeas = {}; nFeas = 0; mlock end if optimValues.funccount == 0 || (isempty(optimValues.fval) && isempty(optimValues.ineq)) % no function evals or none of the trials are successfully evaluated; no plots. return; end updateLegend = false; if isempty(optimValues.fval) % feasibility problem best = optimValues.constrviolation; % incumbent = optimValues.incumbentConstrviolation; current = optimValues.currentConstrviolation; else best = -1*optimValues.fval; % incumbent = optimValues.incumbentFval; current = optimValues.currentFval; end if isempty(plotBest) % xlabel(getString(message('globaloptim:surrogateoptplot:NumFcnEvals'))); xlabel("Number of Cadence simulations") if isempty(optimValues.fval) ylabel(getString(message('globaloptim:surrogateoptplot:Infeasibility'))); title(getString(message('globaloptim:surrogateoptplot:NumFeasiblePts', 0))) else % ylabel(getString(message('globaloptim:surrogateoptplot:ObjectiveFcn'))); % ylabel("OTA gain (dB)") ylabel(optimGoal) title(getString(message('globaloptim:surrogateoptplot:BestIncumbentCurrent',' ',' ',' '))) title("OTA design optimization") end hold on; grid on; end fname = getString(message('MATLAB:optimfun:funfun:optimplots:WindowTitle')); fig = findobj(0,'Type','figure','name',fname); if ~isempty(fig) options = get(fig,'UserData'); tolCon = options.ConstraintTolerance; else tolCon = 1e-3; end if optimValues.constrviolation <= tolCon if isempty(plotBest) plotBest = plot(optimValues.funccount,best,'o','SeriesIndex',1); set(plotBest,'Tag','surrplotbestf','MarkerSize',6); legendHndl(end+1) = plotBest; legendStr{end+1} = getString(message('globaloptim:surrogateoptplot:Best')); updateLegend = true; else newX = [get(plotBest,'Xdata') optimValues.funccount]; newY = [get(plotBest,'Ydata') best]; set(plotBest,'Xdata',newX, 'Ydata',newY); end else if isempty(plotBestInfeas) plotBestInfeas = plot(optimValues.funccount,best,'o','SeriesIndex',2); set(plotBestInfeas,'Tag','surrplotbestfinfeas','MarkerSize',5); legendHndlInfeas(end+1) = plotBestInfeas; legendStrInfeas{end+1} = getString(message('globaloptim:surrogateoptplot:BestInfeas')); updateLegend = true; else newX = [get(plotBestInfeas,'Xdata') optimValues.funccount]; newY = [get(plotBestInfeas,'Ydata') best]; set(plotBestInfeas,'Xdata',newX, 'Ydata',newY); end end if strcmp(optimValues.currentFlag,'initial') if optimValues.currentConstrviolation <= tolCon if isempty(plotInitial) plotInitial = plot(optimValues.funccount,current,'d','SeriesIndex',6); set(plotInitial,'Tag','surrplotinitial','MarkerSize',4); legendHndl(end+1) = plotInitial; legendStr{end+1} = getString(message('globaloptim:surrogateoptplot:InitialSamples')); nFeas = nFeas + 1; updateLegend = true; else newX = [get(plotInitial,'Xdata') optimValues.funccount]; newY = [get(plotInitial,'Ydata') current]; set(plotInitial,'Xdata',newX, 'Ydata',newY); nFeas = nFeas + numel(newY); end else if isempty(plotInitialInfeas) plotInitialInfeas = plot(optimValues.funccount,current,'d','SeriesIndex',2); set(plotInitialInfeas,'Tag','surrplotinitialinfeas','MarkerSize',3); legendHndlInfeas(end+1) = plotInitialInfeas; legendStrInfeas{end+1} = getString(message('globaloptim:surrogateoptplot:InitialSamplesInfeas')); updateLegend = true; else newX = [get(plotInitialInfeas,'Xdata') optimValues.funccount]; newY = [get(plotInitialInfeas,'Ydata') current]; set(plotInitialInfeas,'Xdata',newX, 'Ydata',newY); end end end if optimValues.surrogateReset == 1 && optimValues.funccount > 1 y = get(gca,'Ylim'); x = optimValues.funccount; ll = plot([x, x],y,'SeriesIndex',1); updateLegend = true; if optimValues.surrogateResetCount < 2 legendHndlInfeas(end+1) = ll; legendStrInfeas{end+1} = getString(message('globaloptim:surrogateoptplot:SurrogateReset')); end end if optimValues.checkpointResume && optimValues.funccount > 1 y = get(gca,'Ylim'); x = optimValues.funccount; ll = plot([x, x],y,'LineWidth',0.75,'SeriesIndex',2); updateLegend = true; if optimValues.checkpointResumeCount < 2 legendHndlInfeas(end+1) = ll; legendStrInfeas{end+1} = getString(message('globaloptim:surrogateoptplot:CheckpointResume')); end end if updateLegend && optimValues.constrviolation <= tolCon legend([legendHndlInfeas legendHndl], [legendStrInfeas legendStr]); elseif updateLegend legend(legendHndlInfeas, legendStrInfeas); end if strcmp(state, 'done') hold off; munlock end end