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; pkg load instrument-control [y0 rate bits] = wavread("/usr/share/skype/sounds/CallRingingIn.wav"); 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.
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.
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, firstname.lastname@example.org, 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, email@example.com, 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:
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!
We will try to maintain a post rate of ~ 6,7 posts/week blogging about various electronics/photonoics fundamentals we ususally face every day. Let's see how it goes :)
Back to basics Sunday with me desperately twisting my tongue while trying to show a demo of additive dither.
A large credit goes to Oscar, who helped me with the camerawork. We had to do this in three rounds, first roung my old PC passed out, the second round we managed squeeze-out a recording but the microphone was not on, finally the last round went somewhat better :)