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 vcd add -r sim:/phasegen/* 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!