Main Content

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

Download the OTA design database attached with this example as a zipped folder. 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.

  1. Setup optimization specifications in the Outputs Setup of Cadence maestro view.

  2. 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.

  3. Start the optimization routine which results in several simulation runs. Optimization routine stops when the goals or stop criteria are met.

  4. 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 setp size of 0.1 um, set the parameter as 3e-6:0.1e-6:10e-6. Similarly, if the parameter M24W can vary between 3 um and 40 um with a setp size of 0.1 um, set the parameter as 3e-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)...
goalName = constraintTable.Name{constraintTable.Weight==minWeight};
if length(objectiveLimit) > 1
    warning("Enter unique values for weights.")
    objectiveLimit = objectiveLimit(1);    

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);
% Check if option for Checkpoint file 
if useCheckPointFile == false
    [sol,fval,eflag,output,trials] = surrogateopt(objFunction,lower_bounds,upper_bounds,intcon,options);    
    [sol,fval,eflag,output,trials] = surrogateopt('mycheckpoint',options);

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);

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;

% 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;

% 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;
paramList = strings(1,size(parameters,2));
for param = parameters     
    if isParameter
        paramValue = adeGet(adeInfo=adeInfoIn, paramName=param{1});
        paramValue = adeGet(adeInfo=adeInfoIn, varName=param{1});
    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;
% Make sure there is no value set to a matched parameter including ""

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

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))),':',...
% optimizationVariables.Value = (lb.*variableScaleFactor:variableScaleFactor:ub.*variableScaleFactor);
optimizationVariables.Value = paramValues';
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;
        posneg(ii,1) = 1;
% add the columns %%
% 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;
% Remove unwanted columns:
% objConstrTable.Save=[];

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
        metricVal = cell2mat(metricVal);
    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;
    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;
        ineq(:,iiq) = metricScaled - constraintTable.ScaledConstr(iic);
        iiq = iiq + 1; % index for ineq

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.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;

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)';

function setFinalSolution(names, values)
% set parameters
adeSet(paramName=names, paramValue=values);

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;

if optimValues.funccount == 0 || (isempty(optimValues.fval) && isempty(optimValues.ineq))
    % no function evals or none of the trials are successfully evaluated; no plots.
updateLegend = false;

if isempty(optimValues.fval)
    % feasibility problem
    best = optimValues.constrviolation;
    % incumbent = optimValues.incumbentConstrviolation;
    current = optimValues.currentConstrviolation;
    best = -1*optimValues.fval;
    % incumbent = optimValues.incumbentFval;
    current = optimValues.currentFval;

if isempty(plotBest)
    % xlabel(getString(message('globaloptim:surrogateoptplot:NumFcnEvals')));
    xlabel("Number of Cadence simulations")
    if isempty(optimValues.fval)
        title(getString(message('globaloptim:surrogateoptplot:NumFeasiblePts', 0)))
        % ylabel(getString(message('globaloptim:surrogateoptplot:ObjectiveFcn')));
        % ylabel("OTA gain (dB)")
        title(getString(message('globaloptim:surrogateoptplot:BestIncumbentCurrent',' ',' ',' ')))
        title("OTA design optimization")
    hold on; grid on;

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;
    tolCon = 1e-3;
if optimValues.constrviolation <= tolCon
    if isempty(plotBest)
        plotBest = plot(optimValues.funccount,best,'o','SeriesIndex',1);
        legendHndl(end+1) = plotBest;
        legendStr{end+1} = getString(message('globaloptim:surrogateoptplot:Best'));
        updateLegend = true;
        newX = [get(plotBest,'Xdata') optimValues.funccount];
        newY = [get(plotBest,'Ydata') best];
        set(plotBest,'Xdata',newX, 'Ydata',newY);
    if isempty(plotBestInfeas)
        plotBestInfeas = plot(optimValues.funccount,best,'o','SeriesIndex',2);
        legendHndlInfeas(end+1) = plotBestInfeas;
        legendStrInfeas{end+1} = getString(message('globaloptim:surrogateoptplot:BestInfeas'));
        updateLegend = true;
        newX = [get(plotBestInfeas,'Xdata') optimValues.funccount];
        newY = [get(plotBestInfeas,'Ydata') best];
        set(plotBestInfeas,'Xdata',newX, 'Ydata',newY);

if strcmp(optimValues.currentFlag,'initial')
    if optimValues.currentConstrviolation <= tolCon
        if isempty(plotInitial)
            plotInitial = plot(optimValues.funccount,current,'d','SeriesIndex',6);
            legendHndl(end+1) = plotInitial;
            legendStr{end+1} = getString(message('globaloptim:surrogateoptplot:InitialSamples'));
            nFeas = nFeas + 1;
            updateLegend = true;
            newX = [get(plotInitial,'Xdata') optimValues.funccount];
            newY = [get(plotInitial,'Ydata') current];
            set(plotInitial,'Xdata',newX, 'Ydata',newY);
            nFeas = nFeas + numel(newY);
        if isempty(plotInitialInfeas)
            plotInitialInfeas = plot(optimValues.funccount,current,'d','SeriesIndex',2);
            legendHndlInfeas(end+1) = plotInitialInfeas;
            legendStrInfeas{end+1} = getString(message('globaloptim:surrogateoptplot:InitialSamplesInfeas'));
            updateLegend = true;
            newX = [get(plotInitialInfeas,'Xdata') optimValues.funccount];
            newY = [get(plotInitialInfeas,'Ydata') current];
            set(plotInitialInfeas,'Xdata',newX, 'Ydata',newY);

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'));        

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'));

if updateLegend && optimValues.constrviolation <= tolCon    
    legend([legendHndlInfeas legendHndl], [legendStrInfeas legendStr]);
elseif updateLegend
    legend(legendHndlInfeas, legendStrInfeas);

if strcmp(state, 'done')
    hold off;

See Also

Related Topics