homelist of postsdocs about & FAQ

A few useful functions for Virtuoso layout

I am waiting for a simulation to finish and am a bit bored having done layout whole day. Let me post some very simple but useful SKILL finctions for the layout editor of Cadence's tools.

The first one I use regularly adds a specific layout instance when invoked. I have binded it to various keys and thus can add cells from my own common library, e.g. VIA_M1_M2_min, VIA_M2_M3_min, M1_PO_min, M1_PO_minx2, M1_AA_min etc...

Now you might argue that there are normally provided p-cells for thus purpose, which is true. However, for the image sensor processes I use, this "feature" normally is not provided by the foundries. Or at least, the ones I've used so far haven't got any p-cells. A quick solution is thus having own cell library and adding layout views from it.

I also kind of like that freestyle layout drawing as it gives you the freedom to do "virtuoso stuff", which you normally can't do with p-cells, or it is tedious to set. Besides, p-cells rely on SKILL and Virtuoso's ROD engine, if that setup somehow fails, then the whole design can not be "compiled" / "viewed". Anyway, here is how I instantiate my cells:

;A function to add specified layout instances

procedure(placeInstance(lib cell view "t")
leSetEnv("instLibName" lib)
leSetEnv("instCellName" cell)
leSetEnv("instViewName" view)
hiRegTimer("hiToggleEnterForm()" 1)
leHiCreateInst()
)

;Binding it to a key:
hiSetBindKey("Layout" "Ctrl Alt1" "placeInstance(\"commonLib\" \"M1_M2_min\" \"layout\")")


You can set hiRegTimer("hiToggleEnterForm()" 1) to 0 if you want the enter form to appear and not be auto-minimized, or on the other way around '1' would auto-minimize the window and you are good to go.

Another quick and handy layout bindkey function is the toggle layout: Here is how we can toggle layer visibility, I know there's tons of resources on that, but still may be useful to have a yet another source.

;Toggle layer selectability procedure for metal layers

procedure( toggleMetal(num "x")
let(((metlay list(sprintf(nil "M%d" num) "drawing")) )
if(leIsLayerSelectable(metlay) then
leSetLayerSelectable(metlay nil)
else
leSetLayerSelectable(metlay t)
)
hiRedraw()
)
)


We can set it to an arbitraty layer defined in our techlib by minor corrections. We can even choose to toggle schematic editor layers.

Alternatively, we can also toggle visibility which is one of my favorite functions:

;Toggle layer visibility procedure

procedure( CCStoggleNW(num "x")
let(((metlay list(sprintf(nil "NW" num) "drawing")) )
if(leIsLayerVisible(metlay) then
leSetLayerVisible(metlay nil)
else
leSetLayerVisible(metlay t)
)
hiRedraw()
)
)


I guess the most important stress in this post, and in doing fast and efficient layout (bar good floorplanning and thinking in advance), is the good set of bindkey functions. Try to use the mouse as less frequent as possible, i.e. only for drawing or zooming-in-out.

Date:Tue Apr 28 17:45:49 CET 2015

"Alchymia" in circuit design

I stumbled upon an interesting book - The Collected Papers of Lewis Fry Richardson. Richardson was an English scientist of "everything" - maths, physics, psychology, meteorology... actually a pioneer of modern day mathematical techniques of weather forecasting.

Now all that is great, but what got my attention is the following figure: (I hope reprinting figures from Richardson's papers from 1930s is not a severe copyright violation)

This all looks to me like some crazy alchemistry in circuit design. No matter how many times I tried to read this paragraph it all simply does not make any sense at all. It really is fascinating how great scientists in the past tried all sorts of crazy ideas (including the addition of an electric eel to an RLC circuit) to see "if something great would happen".

But wait, there is even more:

It all smells some deep alchemistry happenning around. But hey, if I can, why not?

Date:Tue Apr 19 14:07:40 CET 2015

Fun with the parallel (LPT) port

Recently I participated in an applied electronics seminar at the University of Ruse, which aimed at promoting the electronics "sub-subject" to high school and first-year students. I decided to talk a bit about CMOS integrated circuits and let the students play with the parallel port of a PC and make them play some music over the LPT port through an R2R DAC. It ended-up being quite fun, let me briefly show you what I used and how easy it was :) Below you can watch a video with the very first enlivening of the "player".

I used randomly picked (not even exactly R and 2R) resistors to form an R2R ladder which I then connected to the parallel port's 8-bit data bus. For the rest (LPT control) I used octave and the instrument-control package provided to directly access the LPT port and dump vector streams to it. Unfortunately "instrument-control" does not provide us with sampling rate functions as this is purely OS/octave handling dependent. Nevertheless we can measure it and provide interpolated samples to the DAC to suit our needs, or to keep a somewhat reasonable sample-time base close to the real one in the files which we are playing. Here is a minimalistic code to make things work.

%clc;
%clear all;

N = 8;

y=round(y0*(2^N/2)+(2^N/2)); % scale to 8 bits 0-255 and round to nearest integer

nSmpl = length(y);

pp = parallel("/dev/parport0", 0);

for k = 1:nSmpl
pp_data(pp,y(k));
%pp_data(pp,255);  % flash port to measure LPT port sampling rate
%pp_data(pp,0);
end

pp_close(pp);


Also, here are some pictures from the seminar we organized.

ROUTE TO THE PICTURE GALLERY

Date:Tue Apr 16 10:02:34 CET 2015

Ideal ramp generator

Why not go back to basics and show how an ideal ramp voltage generator can be implemented in Virtuoso by using simple ideal components from the analogLib library.

We can use a voltage controlled current source (vccs) driving current into a capacitor and forming a negative feedback and employing a second voltage source which sets the maximum ramp voltage level. An ideal switch is ised for capacitor discharge (reset), and as long as we have the switch turned off our generator starts ramping.

The slew rate can be controlled by changing the maximum deliverable current by our vccs. Some cell parametrization seems useful in our case. You can get this component here.

Date:Tue Feb 06 11:53:21 CET 2015

Value Change Dumps and SPICE

Often when one is working with SPICE simulations on a relatively big switched capacitor/mixed-signal circuits, multiple synchronized clock sources are required. Using ordinary pulse and pwl voltage sources can often become quite a lengthy and error-prone task. Yet, if you are using the spectre simulator you have the bit source and pwlf features, but often these are also not enough.

Most spice simulators however would most often support vector and value change dump files. While a vector file can be easier to write manually it can often be quite tedious to compile too. Using some sort of perl text processing scripts can ease the job a lot, but this will always have to involve some hacking from your side. Usually every designer has his own ways of simulating circuits, there is plenty of information online about how one actually does this. However even though that these are fairly simple and trival techniques, I often see designers who are not actually aware of these simulator capabilities, or they think that trying to automate this task often leads to a waste of time. Well, I offer you some simple tricks on how to easily generate signals without too much hassle in HDL.

There are plenty of open-source HDL simulators check out this page. For my case, as I have access to Model Sim, I generate my vcd dumps with this tool. Note that one needs a limited experience with VHDL to be able to follow this approach. You can generate a few functions or procedures which you can use for toggling of various signals with some global time variables. Here is an example of a simple toggle procedure:

----------------------------------------------------
-- VHDL code for input stimuli for jcRampAD
-- "toggle" function call definitions
--
-- Version P1A - Deyan Levski, deyan.levski@eng.ox.ac.uk, 17 Jan 2015
--
--
----------------------------------------------------
library ieee ;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

package counters is

--function toggle (constant BOUND_L, BOUND_H :integer) return std_logic;
--

procedure toggle (
pre_ut    : out std_logic;
BOUND_L   : in    integer;
BOUND_H   : in    integer;
BOUND_G   : in    integer;
cnt       : inout integer);

procedure dualtoggle (
pre_ut    : out std_logic;
BOUND_L   : in    integer;
BOUND_H   : in    integer;
BOUND_G   : in    integer;
BOUND_LP  : in    integer;
BOUND_HP  : in    integer;
cnt       : inout integer);

procedure divide (
clk_in  : in std_logic;
reset   : in std_logic;
clk_out : inout std_logic;
div     : in integer;
cnt     : inout integer;
cnt_per : inout integer;
BOUND_L : in integer;
BOUND_H : in integer;
BOUND_G : in integer);

end;

package body counters is

procedure toggle (pre_ut      : out   std_logic;
BOUND_L    : in    integer;
BOUND_H    : in    integer;
BOUND_G    : in    integer;
cnt        : inout integer) is

begin  -- toggle

if cnt >= BOUND_L and cnt < BOUND_H then
pre_ut := '1';
else
pre_ut := '0';
end if;

if cnt = BOUND_G then
cnt := 0;
end if;

cnt := cnt + 1;

end procedure toggle;

procedure dualToggle (pre_ut      : out   std_logic;
BOUND_L    : in    integer;
BOUND_H    : in    integer;
BOUND_G    : in    integer;
BOUND_LP   : in    integer;
BOUND_HP   : in    integer;
cnt        : inout integer) is
begin  -- dualToggle

if (cnt >= BOUND_L and cnt < BOUND_H) or (cnt >= BOUND_LP and cnt < BOUND_HP) then
pre_ut := '1';
else
pre_ut := '0';
end if;

if cnt = BOUND_G then
cnt := 0;
end if;

cnt := cnt + 1;

end procedure dualToggle;

procedure divide ( clk_in  : in std_logic;
reset   : in std_logic;
clk_out : inout std_logic;
div     : in integer;
cnt     : inout integer;
cnt_per : inout integer;
BOUND_L : in integer;
BOUND_H : in integer;
BOUND_G : in integer) is

begin -- divide

if (reset = '1') then
clk_out := '0';
cnt := 0;
cnt_per :=0;

else

if (cnt_per >= BOUND_L and cnt_per < BOUND_H ) then

if (cnt = div) then
clk_out := NOT(clk_out);
cnt := 0;
else
cnt := cnt + 1;
end if;

else
clk_out := '0';
end if;

if cnt_per = BOUND_G then
cnt_per := 0;
end if;

cnt_per := cnt_per + 1;

end if;

end procedure divide;

end package body;


Once you have made your library with custom toggling and clock functions, you can use your functions in combination with a set of constants which define the on and off time of each signal. Here is an example:

----------------------------------------------------
-- VHDL code for input stimuli for jcRampAD
--
-- Version P1A - Deyan Levski, deyan.levski@eng.ox.ac.uk, 17 Jan 2015
--
--
----------------------------------------------------
library ieee ;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use work.counters.all;

entity phasegen is

generic(n: natural :=2);

port(	clock:		in std_logic;
clear:		in std_logic;
d_count_clk:		out std_logic;
d_count_inv_clk:	out std_logic;
d_comp_out: out std_logic;
d_count_en:	out std_logic;
d_count_rst:	out std_logic;
d_count_hold: out std_logic;
d_count_updn: out std_logic;
d_count_inc_one: out std_logic;
);

end phasegen;

----------------------------------------------------

architecture behv of phasegen is

constant GLOBALPRESCALER : integer := 8;
constant GLOBALPER : integer := 1000*GLOBALPRESCALER;

constant CLKDIV : integer := 4;
constant CLKDIV_L : integer := 102*GLOBALPRESCALER;
constant CLKDIV_H : integer := 228*GLOBALPRESCALER;

constant COUNTINVCLK_L : integer := 304*GLOBALPRESCALER;
constant COUNTINVCLK_H : integer := 310*GLOBALPRESCALER;

constant COMPOUT_L : integer := 80*GLOBALPRESCALER;
constant COMPOUT_H : integer := 130*GLOBALPRESCALER;
constant COMPOUT_G : integer := 2*GLOBALPER;
constant COMPOUT_LP : integer := 1080*GLOBALPRESCALER;
constant COMPOUT_HP : integer := 1132*GLOBALPRESCALER;

constant COUNTEN_L : integer := 100*GLOBALPRESCALER;
constant COUNTEN_H : integer := 230*GLOBALPRESCALER;

constant COUNTRST_L : integer := 50*GLOBALPRESCALER;
constant COUNTRST_H : integer := 60*GLOBALPRESCALER;
constant COUNTRST_G : integer := 2*GLOBALPER;

constant COUNTHOLD_L : integer := 302*GLOBALPRESCALER;
constant COUNTHOLD_H : integer := 320*GLOBALPRESCALER;

constant COUNTUPDN_L : integer := 314*GLOBALPRESCALER;
constant COUNTUPDN_H : integer := 2000*GLOBALPRESCALER;
constant COUNTUPDN_G : integer := 2*GLOBALPER;

constant COUNTINCONE_L : integer := 50*GLOBALPRESCALER;
constant COUNTINCONE_H : integer := 60*GLOBALPRESCALER;
constant COUNTINCONE_G : integer := GLOBALPER;
constant COUNTINCONE_LP : integer := 316*GLOBALPRESCALER;
constant COUNTINCONE_HP : integer := 320*GLOBALPRESCALER;

signal pre_d_count_clk: std_logic;
signal pre_d_count_inv_clk: std_logic;
signal pre_d_comp_out: std_logic;
signal pre_d_count_en: std_logic;
signal pre_d_count_rst: std_logic;
signal pre_d_count_hold: std_logic;
signal pre_d_count_updn: std_logic;
signal pre_d_count_inc_one: std_logic;

begin

-- behavioral description of the counter

process(clock, clear)

variable clkdiv_cnt : integer :=0;
variable clkdiv_cnt_per : integer :=0;
variable d_count_inv_clk_cnt : integer :=0;
variable d_comp_out_cnt : integer :=0;
variable d_count_en_cnt : integer :=0;
variable d_count_rst_cnt : integer :=0;
variable d_count_hold_cnt : integer :=0;
variable d_count_updn_cnt : integer :=0;
variable d_count_inc_one_cnt : integer :=0;
variable d_jc_delay_flag : integer :=0;

variable pre_d_count_clk_var : std_logic;
variable pre_d_count_inv_clk_var : std_logic;
variable pre_d_comp_out_var : std_logic;
variable pre_d_count_en_var : std_logic;
variable pre_d_count_rst_var : std_logic;
variable pre_d_count_hold_var : std_logic;
variable pre_d_count_updn_var : std_logic;
variable pre_d_count_inc_one_var : std_logic;

begin

if clear = '1' then

clkdiv_cnt :=0;
clkdiv_cnt_per :=0;
d_count_inv_clk_cnt :=0;
d_comp_out_cnt :=0;
d_count_en_cnt :=0;
d_count_rst_cnt :=0;
d_count_hold_cnt :=0;
d_count_updn_cnt :=0;
d_count_inc_one_cnt :=0;

pre_d_count_clk_var :='0';
pre_d_count_inv_clk_var :='0';
pre_d_comp_out_var :='0';
pre_d_count_en_var :='0';
pre_d_count_rst_var :='0';
pre_d_count_hold_var :='0';
pre_d_count_updn_var :='0';
pre_d_count_inc_one_var :='0';

elsif (clock = '1' and clock'event) then

divide(clock, clear, pre_d_count_clk_var, CLKDIV, clkdiv_cnt, clkdiv_cnt_per, CLKDIV_L, CLKDIV_H, GLOBALPER);

toggle(pre_d_count_inv_clk_var, COUNTINVCLK_L, COUNTINVCLK_H, GLOBALPER, d_count_inv_clk_cnt);

dualToggle(pre_d_comp_out_var, COMPOUT_L, COMPOUT_H, COMPOUT_G, COMPOUT_LP, COMPOUT_HP, d_comp_out_cnt);

toggle(pre_d_count_en_var, COUNTEN_L, COUNTEN_H, GLOBALPER, d_count_en_cnt);
toggle(pre_d_count_rst_var, COUNTRST_L, COUNTRST_H, COUNTRST_G, d_count_rst_cnt);
toggle(pre_d_count_hold_var, COUNTHOLD_L, COUNTHOLD_H, GLOBALPER, d_count_hold_cnt);
toggle(pre_d_count_updn_var, COUNTUPDN_L, COUNTUPDN_H, COUNTUPDN_G, d_count_updn_cnt);

dualToggle(pre_d_count_inc_one_var, COUNTINCONE_L, COUNTINCONE_H, COUNTINCONE_G, COUNTINCONE_LP, COUNTINCONE_HP, d_count_inc_one_cnt);

pre_d_count_clk <= pre_d_count_clk_var;
pre_d_count_inv_clk <= pre_d_count_inv_clk_var;
pre_d_comp_out <= pre_d_comp_out_var;
pre_d_count_en <= pre_d_count_en_var;
pre_d_count_rst <= pre_d_count_rst_var;
pre_d_count_hold <= pre_d_count_hold_var;
pre_d_count_updn <= pre_d_count_updn_var;
pre_d_count_inc_one <= pre_d_count_inc_one_var;

end if;

end process;

-- concurrent assignment statement
d_count_clk <= pre_d_count_clk;
d_count_inv_clk <= pre_d_count_inv_clk;
d_comp_out <= pre_d_comp_out;
d_count_en <= pre_d_count_en;
d_count_rst <= pre_d_count_rst;
d_count_hold <= pre_d_count_hold;
d_count_updn <= pre_d_count_updn;
d_count_inc_one <= pre_d_count_inc_one;

end behv;

-----------------------------------------------------



After writing, a quick simulation in ModelSim should provide you with the basic waveforms depending the data you have defined in the constants section. You can also play with the main clock period, to stretch all waveforms in time and thus for example increase or decrease the global settling time constraints or the data conversion time and so on. Here is a picture of the code written above and its behabior.

Once you have tweaked your code so that it behaves to your needs you can move over to the vcd generation part. For ModelSim start by simply:

vcd files dump.vcd

run 170000


This will simply create a value comma dump file with a length of 170000 time units, ModelSim's default is in ns. After having the dump file ready you can simple procede to adding it as a source file to your simulator run file. If you are using spectre you can include a vcd file to your simulation with the following expression:

vcd_include "/path/to/your/include/file/dump.vcd"

Now we also have to generate a vcdinfo file, as otherwise spectre won't know how to interpret the ones and zeros, an example file should look like this:

.in signal1 signal2 net35 myclock reset
.out testtoggle

.vih 3.3
.vil 0
.trise 1p
.tfall 1p


Once this has been all set up we should get the same waveforms into SPICE.

A simple, yet quite flexible way of handling more signals and feeding them directly into a spice simulator. There is a lot written out there, but somehow such schemes are not widely spread amongst designers and the knowledge is rather kept inside companies, whilst ordinary hmmm... researchers still keep using the only the GUI tools.

Anyway, I hope this is helpful to someone, even though that it is the ultra-basics of digital-analog co-simulations. See you next time!

Date:Tue Jan 20 23:08:21 CET 2015