Monday, August 18, 2014

SystemVerilog: Transaction, driver, monitor, scoreboard, environment

This is my code to verify a switch.
A note of caution when using mailbox, mailbox only holds handles not the object. So if you need to place multiple objects in the mailbox simultaneously, you would need to create (new) objects equivalent number of times.

class packet;
rand bit [47:0] src_addr;
rand bit [31:0] src_data;
 //signal unrelated to rtl
static bit [15:0] pkt_id;
//packet class is no longer talking to RTL, so no need of VI

function new();
pkt_id++;
endfunction

virtual function void print();
$display("src_addr = %h, src_data = %h", src_addr, src_data);
endfunction

virtual function void rec_print();
$display("dst_addr = %h, dst_data = %h", src_addr, src_data);
endfunction

virtual function packet deepcopy();
//$display("I am here: in deepcopy in packet class");
packet funny;
funny = new();
return(funny);
endfunction

endclass

///////////////////////////////////////////////////////////////////////

class transaction extends packet;
virtual function packet deepcopy(); //has to match prototype
packet pkt_deepcopy;
pkt_deepcopy = new();
pkt_deepcopy.src_data = this.src_data;
pkt_deepcopy.src_addr = this.src_addr;
return(pkt_deepcopy);
endfunction
//function pack();
//function unpack();
endclass


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// driver must be container class to talk to class packet, class sb

class driver;
packet pkt;
virtual switch_interface vi;
mailbox drv2sb;

function new(input virtual switch_interface vif, input mailbox mb);
this.vi = vif;
this.drv2sb = mb;
endfunction

task send_packet();
pkt = new();
assert(pkt.randomize());
@(posedge vi.clk);
vi.src_addr <= pkt.src_addr;
@(posedge vi.clk);
vi.src_data <= pkt.src_data;

pkt.print();
drv2sb.put(pkt);
endtask

endclass

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class monitor;
virtual switch_interface mi;
packet rcv_pkt;
mailbox mon2sb;
function new(input virtual switch_interface mif, input mailbox mb);
this.mi = mif;
this.mon2sb = mb;
endfunction

task collect_packet();
rcv_pkt = new();
@(posedge mi.clk);
rcv_pkt.src_addr = mi.cb.dst_addr;
@(posedge mi.clk);
rcv_pkt.src_data = mi.cb.dst_data;
rcv_pkt.rec_print();
mon2sb.put(rcv_pkt);
endtask
endclass

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class scoreboard;
mailbox rcv_from_drv;
mailbox rcv_from_mon;
function new(input mailbox drv2sb, input mailbox mon2sb);
this.rcv_from_drv = drv2sb;
this.rcv_from_mon = mon2sb;
endfunction

task compare (input mailbox rcv_from_drv, input mailbox rcv_from_mon);
bit error;
packet pkt_from_drv;
packet pkt_from_mon;
rcv_from_drv.get(pkt_from_drv);
rcv_from_mon.get(pkt_from_mon);
pkt_from_drv.print();
pkt_from_mon.rec_print();

if (pkt_from_mon.src_addr !== (pkt_from_drv.src_addr + 1)) begin
$display("time=%0t ERROR: Packet Mismatch, wrong addr", $time);
error++;
end
else if (pkt_from_mon.src_data !== (pkt_from_drv.src_data + 1)) begin
$display("time=%0t ERROR: Packet Mismatch, wrong data", $time);
error++;
end
else $display("time=%0t PASS: Packet Match", $time);
endtask
endclass

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

class env;
driver drv;
monitor mon;
virtual switch_interface vi;
virtual switch_interface mi;
//add sb n mailbox to env neighbourbood
scoreboard sb;
mailbox drv2sb;
mailbox mon2sb;

function new(input virtual switch_interface vif, input virtual switch_interface mif);
this.vi = vif;
this.mi = mif;

//create objects (data members)
drv2sb = new();
mon2sb = new();

//hand over drv2sb to drv + sb, and mon2sb to mon + sb
drv = new(vif, drv2sb);
mon = new(mif, mon2sb);
sb = new(drv2sb, mon2sb);
endfunction

//env directs driver to send wiggle through run task
task run(input int count_packet);
for(int i=0; i <count_packet; i++) begin
   @(posedge vi.clk) drv.send_packet();
   @(posedge mi.clk) mon.collect_packet();
   sb.compare(drv2sb, mon2sb);
end
endtask
endclass
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

`include "packet6.sv"

program testcase(switch_interface tcifdriver, switch_interface tcifmonitor);

/*class packet_fixed_src_data extends packet;
constraint fixed_src_data { src_data == 32'hbabeface;
}
endclass*/

env env0;
int num_packet;
//packet_fixed_src_data testcasepacket;
packet pkt;
packet deep;
transaction deep_base;

initial begin
env0 = new(tcifdriver, tcifmonitor);

num_packet=$urandom_range(4,32);
//env0.run(num_packet);

pkt = new();
assert(pkt.randomize());
$display("Printing pkt");
pkt.print();
deep = new();
deep = pkt.deepcopy();
$display("Printing deep, after deepcopy from packetclass (just a template function, doesnt actually perform deepcopy)");
deep.print();
deep_base = new();
assert(deep_base.randomize());
$display("Printing deep_base");
deep_base.print();
deep = deep_base.deepcopy();
$display("Printing deep, after deepcopy from extended class");
deep.print();
#100 $finish;
end
endprogram

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

module testbench();
bit [47:0] dst_addr;
bit [31:0] dst_data;
bit clk;
bit sop, eop;
bit valid;
always #10 clk = ~clk;

switch_interface sif (.clk (clk));

switch sw0 (
.clk (clk),
.src_addr (sif.src_addr),
.src_data (sif.src_data),
.dst_addr (sif.dst_addr), //monitor
.dst_data (sif.dst_data)
);

testcase itest (.tcifdriver (sif.testcase_port),
 .tcifmonitor (sif.testcase_port));

endmodule



SystemVerilog: transaction class with virtual interface

Another place to put a clocking block is inside the interface. The direction of signals is with respect to testcase.

interface switch_interface(input clk);
logic [47:0] src_addr;
logic [31:0] src_data;
logic [47:0] dst_addr;
logic [31:0] dst_data;
bit sop, eop, valid;

default clocking cb @(posedge clk);
output #1 src_addr, src_data, sop, eop;
input #2 dst_addr, dst_data, valid;
endclocking

modport testcase_port (input clk, clocking cb);

modport switch_port (input clk, src_addr, src_data, sop, eop);

endinterface


//using vi + clocking block
class packet;
rand bit [47:0] src_addr;
rand bit [31:0] src_data;
 //signal unrelated to rtl
static bit [15:0] pkt_id;
virtual switch_interface vi;

function new(input virtual switch_interface vif);
this.vi = vif; //vi now pointing to actual interface passed in new
pkt_id++;
endfunction

task send_packet();
@(posedge vi.clk);
vi.cb.sop <= 1'b1;
@(posedge vi.clk);
vi.cb.src_addr <= this.src_addr;
vi.cb.sop <= 1'b0;
repeat(6)@(posedge vi.clk);
vi.cb.src_data <= this.src_data;
repeat(4)@(posedge vi.clk);
vi.cb.eop <= 1'b1;
@(posedge vi.clk);
vi.cb.eop <= 1'b0;
endtask
endclass


`include "packet.sv"
program testcase(switch_interface tcif);
packet testcasepacket;

initial begin
testcasepacket = new();
testcasepacket.randomize();
testcasepacket.send_packet();
#100 $finish;
end
endprogram


module testbench();
bit clk;
always #10 clk = ~clk;

switch_interface sif (.clk (clk));

switch sw0 (
.clk (clk),
.src_addr (sif.src_addr),
.src_data (sif.src_data),
.sop(sif.sop),
.eop(sif.eop),
.valid(sif.valid),
.dst_addr (sif.dst_addr),
.dst_data (sif.dst_data)
);

testcase itest (.tcif (sif.testcase_port));

endmodule

Sunday, August 17, 2014

SystemVerilog: Clocking block + assert statements

We will needto add the keyword default before clocking block if we want to use a ##number_of_cycles delay even if we have just 1 clocking block.
Assign values to variables in clocking block by using blocking assignment.

I used this to actually see the effect of each type of clocking block..added this in initial block of testbench:
`ifdef postprocess
$vcdpluson(0,C1);
$vcdplustraceon(C1);
$vcdplusdeltacycleon;
$vcdplusglitchon;
`endif

program automatic testcase #(parameter WIDTH=4)
                 ( input bit clk,
                   input bit detect,
                   input  logic [WIDTH-1:0]  result,
                   output bit rst,
                   output bit en,
                   output bit pld,
                   output bit mode,
                   output logic [WIDTH-1:0]  preload_data);

`ifdef default_skew
default clocking clk_blk @ (posedge clk);
input detect, result;
output rst, en, pld, mode, preload_data;
endclocking
`endif

`ifdef hash<value>ns_skew
default clocking clk_blk @ (posedge clk);
input #<value1> detect, result;
output #<value2> rst, en, pld, mode, preload_data;
endclocking
`endif

`ifdef 1step_edge_skew
default clocking clk_blk @ (posedge clk);
input #1step detect, result;
output negedge rst, en, pld, mode, preload_data;
endclocking
`endif

initial begin
$monitor ("t=%3t: result=%2d, detect=%b", $time, result, detect);

clk_blk.rst <= 1;
clk_blk.en <= 0;
clk_blk.pld <= 0;
clk_blk.mode <= 0;

repeat (2) @(posedge clk);
clk_blk.rst<=0;

@(posedge clk);
clk_blk.en <= 1;
repeat(17)@(posedge clk);

clk_blk.en <= 0;
repeat (10)@(posedge clk);

$finish;
end

/*Some of the assert statements i used to test different aspects of counter:
assert property (@(posedge clk) result == {WIDTH{1'b1}} -> detect) else flag = 1;
assert property (@(posedge clk) en==0 |=> $stable(result)) else flag = 1;
assert property (@(posedge clk) pld == 1 && en == 1 |=> result == preload_data) else flag = 1;
assert property (@(posedge clk) rst==1 |=> result == {WIDTH{1'b0}}) else flag = 1;


final begin
if (flag) $display ("FAIL");
else $display ("PASS");
end
*/
endprogram


runsim:
if [ -z "$1" ]                           ##true if len of string is 0
   then
     echo "No skew specified, using default skews"
     vcs -full64 +vcs+lic+wait +v2k -sverilog +lint=PCWM -R +define+default_skew -l vcs.log ../../testbench.sv testcase.sv ../../counter.v
     echo "**INFO** Use runsim <skew> to specify skew, valid values for <skew> are default_skew, hash<value>n_skew, 1step_edge_skew"
     echo "**INFO** runsim finished test with +define+default_skew"
else
     echo $1
     vcs -full64 +vcs+lic+wait +v2k -sverilog +lint=PCWM -R +define+$1 -l vcs.log ../../testbench.sv testcase.sv ../../counter.v
     echo "**INFO** runsim finished test with +define+$1"
fi

command to run:
runsim <whtever_you_choose_from_default_skew_etc>


Using tasks:
task init(
          ref bit clk,
  ref bit system_clk,
          output bit rst,
          output bit en,
          output bit pld);
begin
rst <= 1;
en <= 0;
pld <= 0;
end
endtask

`include "tasks.sv"
program automatic testcase #(parameter WIDTH=4)
                 ( ref bit clk, ref bit system_clk,
                   input bit detect,
                   input  logic [WIDTH-1:0]  result,
                   ref bit rst,
                   ref bit en,
                   ref bit pld,
                   ref bit mode,
                   output logic [WIDTH-1:0]  preload_data);

bit flag;
logic [WIDTH-1:0]  X;

default clocking clk_blk @ (posedge clk);
input detect, result;
output rst, en, pld, mode, preload_data;
endclocking


initial begin
$monitor ("t=%3t: en = %b, pld = %b, result=%2d, detect=%b", $time, en, pld, result, detect);
X = $urandom_range(0, {WIDTH{1'b1}});
preload_data = $urandom_range(0, {WIDTH{1'b1}});

init(clk, system_clk, clk_blk.rst, clk_blk.en, clk_blk.pld);
rest of the program follows...

Progressing from Verilog to SysteVerilog: Program

Verilog:
module testbench #(parameter WIDTH=4)();
reg clk, rst, en, pld, mode;
reg [WIDTH-1:0] pld_data;
wire detect;
wire [WIDTH-1:0] result;
counter #(4) C1 (
               .clk (clk),
               .reset (rst),
               .enable (en),
               .preload (pld),
               .preload_data (pld_data),
               .mode (mode),
               .detect (detect),
               .result (result));

initial begin
$monitor ("t=%t: result=%d, detect=%b", $time, result, detect);
clk = 0; 

rst = 1; en = 0; pld = 0; mode = 0;
@(posedge clk) rst=0;

@(posedge clk);
en = 1;

repeat(10)@(posedge clk);
en = 0;

repeat (10)@(posedge clk);
$finish;
end

always
#5 clk = ~clk;
endmodule



SystemVerilog
Note that in the above testbench, the testbench is a module like RTL. This may cause issues. Using a "program" block essentially directs the tool to handle the RTL and stimulus generation aka testcase in separate delta cycle. 
The same code as above, put inside a "program testcase". A module testbench will hookup RTL and testcase. Also note that a program block cannot have always statements. So clock generation is performed in testbench. Initializing clk = 0 in initial block prevents clk = X.
I also had a test when I needed to issue $fisnish if things are idle for say 20 clock cycles. I put the check in testbench using an always block + counter. In my recent project at work, I added this check right in the driver module with a fork-join.  

program testcase #(parameter WIDTH=4)( input wire clk, 
                   input wire detect,
                   input  wire [WIDTH-1:0]  result,
                   output logic rst, 
                   output logic en, 
                   output logic pld, 
                   output logic mode,
                   output logic [WIDTH-1:0]  preload_data);

initial begin
$monitor("check");
$monitor ("t=%t: result=%d, detect=%b", $time, result, detect);

rst = 1; en = 0; pld = 0; mode = 0;

@(posedge clk);
rst=0;

@(posedge clk);
en = 1;
repeat(15)@(posedge clk);

en = 0;
repeat (10)@(posedge clk);

$finish;
end
endprogram


module testbench #(parameter WIDTH=4)();
logic clk;
logic [WIDTH-1:0] pld_data, result1;
testcase #(4) P1 (
               .clk (clk),
               .rst (rst),
               .en (en),
               .pld (pld),
               .preload_data (pld_data),
               .mode (mode),
               .detect (detect1),
               .result (result1)
);

counter #(4) C1 (
               .clk (clk),
               .reset (rst),
               .enable (en),
               .preload (pld),
               .preload_data (pld_data),
               .mode (mode),
               .detect (detect1),
               .result (result1)
);

initial begin
clk = 0;
end

always
#5 clk = ~clk;

endmodule

Run:
vcs +vcs+lic+wait +v2k -sverilog -R -l vcs.log testbench.sv testcase.sv counter.v

Tuesday, May 13, 2014

Interface in SystemVerilog

Interface simplifies hooking up modules/ programs.
There are 2 methods of hooking up stuff using interface.
Hardwire method:
RTL: portname is interface_name.modport_name choose_a_name.
all outputs are assigned as choose_a_name = value.
At top level: create a instance of interface_name.
interface_name instance_of_interface_name (.clk(clk));
and hook up choose_a_name with instance_of_interface_name.

An example will make it clear. I am using an example from my current assignment at the UCSC extension coursework.
module memory_core (memory_interface.core_port deepika_coreif);
   always @ (posedge deepika_coreif.clk)
   ....
endmodule

module memory_ctrl(memory_interface.ctrl_port deepika_ctrlif);
....
endmodule

program testcase(memory_interface.testcase_port deepika_tcif,
input call_finish);
............
endprogram

module top();

   bit         clk;
   memory_interface imif (.clk (clk));
   ..........
   initial begin
clk = 0;
   end

   always #5 clk = !clk;
   ....

   memory_core icore     (imif);
   memory_ctrl         ictrl       (imif);
   testcase   itest      (.deepika_tcif (imif),
     .call_finish (call_finish) );
endmodule


Generic method: 
Here at the RTL end, the porttype is simply interface. What gets hooked up is instance_of_interface.specific_modport.
module memory_core (interface deepika_coreif);
..
endmodule

module memory_ctrl(interface deepika_ctrlif);
...
endmodule

program testcase(interface deepika_tcif,
input call_finish);
..
endprogram

module top();
   bit         clk;
   ....
   memory_interface imif (.clk (clk));
 
   initial begin
clk = 0;
   end
   always #5 clk = !clk;

   memory_core icore     (imif.core_port);
   memory_ctrl ictrl     (imif.ctrl_port);
   testcase   itest   (.deepika_tcif (imif.testcase_port),
     .call_finish (call_finish) );
endmodule

The code for interface itself remains unaffected whichever way you to connect stuff.
interface memory_interface (input bit clk);
logic reset;
.....
//modports
modport core_port(
input clk, reset,...,
output ... );

modport ctrl_port(
input clk, reset, ...
        output ... );
);

modport testcase_port(
input clk, reset...,
       output ...,
       import init_mem,
       import write_mem,
       import read_mem
);

task init_mem();
reset = 1; ....
#100 reset = 0;
endtask

task write_mem(ref int passed_addr,
               ref bit [7:0] passed_data  );
...
endtask

task read_mem( ref int passed_addr);
....
endtask
endinterface

Advantages of using tasks:
In my example, I could use the tasks and fork-join to test what happens when write and read occurs simultaneously  (This could never happen because there is a single read/writebar pin but its fun to check it anyways.. Pretty cool stuff.)

Note that task only has whatever is "passed" as the inputs/outputs. Task is actively driving the outputs but enumerating that is already taken care of in the modport. A good way of simplifying the code.
Interface basically gives you a overview of stuff. Since you consolidate all wires in one file, all worry about data_width mismatch, or whether you connected little/ big endianess correctly is taken care of.

Monday, May 5, 2014

My experiments with hunger

So I was scheduled for a wisdom tooth extraction in the new place that I shifted to. Since I dont have a car here, public transport it was, and I am not allowed to eat in the bus! So the only time I could get a quick bite was between 2 buses. And then the tooth extraction happened, so I was not supposed to eat. I kind of spent 3 days on either partial lunch (day0) or liquid food (aka milk with kheericha sattva, shikaran, or jamba juice). I dont have a calorie count but my estimate from how hungry/ weak i felt and how gaunt I looked is that it was far less, may be a half of the recommended 2000. This was totally a first world problem - I could not go out for more Jamba because I didn't have a car, and I could not go out in general because I was too weak.

There are billions of people who probably eat as little as I did and on top of it do physical labor to manage to earn that food. I kind of remember reading that jowar/ bajra/ millet are easier to digest, and I do hope that they are so easy to digest that those poor people who survive only on zhunka bhakar can extract all of its nutrition from the meager food they eat.

A few days ago I had participated in the SNAP challenge to eat only in a 4.5$ a day. The background story is that the US government fixed an upper limit on food stamps that each poor person will get: food stamps amounting to only 4.5$ a day. Then there are these well-to-do challenge takers who basically realized at the end of the challenge that this limit was too little, and how hard life is for poor people. The SNAP website is full of testimonials from people who claimed how hungry and snappy they were in managing to eat in just 4.5$ and all.
Living in Florida, I shop all my groceries including Organic fruits and veggies for the week in under 30$ which means that I trump the challenge each week without even trying, inspite of shopping in Publix (which is costlier than walmart), shopping Organic (which is costlier than non-organic) and being a vegetarian (because apparently meat is cheaper than veggies in the US). What I had not factored in was that there are states with higher taxes than Florida, or states with Federal taxes on top of the state taxes. (Or the cost of electricity to actually cook that rice). My conclusion from my normal, challenge shopping without ever taking up the challenge was that poor people should buy in bulk, and cook at home, and then 3 meals for 4.5$ a day per person isn't a big deal. Actually cooking for more people got progressively cheaper.
What was unrealistic was that my first world perspective assumed that people have a place where they can store the milk and fruits in a refrigerator, or store the rice bought in bulk pestfree, and have access to stove. Dasani's story changed my opinion.

Since my grocery bills were always lower than the SNAP, I never really actively took the challenge. Being on a liquid diet for a totally different reason opened my eyes to how bad really the life is for people who survive on half meals every day. 

Thursday, April 17, 2014

SystemVerilog, fork-join, parallel threads

The trick to have parallel threads is to use "automatic" keyword when you define a task.
This is just another perspective I tried out while practicing.
file task.sv
class show1;
int   delay;
bit [7:0] count;

function new (int delay, bit [7:0] count);
this.delay = delay;
this.count = count;
endfunction : new;

task show();
            #(delay) $display("time=%2t: Delay=%2d Count=%2d", $time, delay, count);
endtask
endclass



file testcase.sv
`include "./tasks.sv"
program testcase;
show1 s1, s2, s3, s4, s5, s6;
   initial begin
     fork
       s1= new (3,2);
       s1.show();
       s2 = new (5,8); s2.show();
       s3 = new (10,25); s3.show();
       s4 = new (23,0); s4.show();
       s5 = new (6,1); s5.show(); 
     join
     $display("time=%3t: Exiting fork-join...\n", $time);
     s6 = new (5, 3); s6.show(); 
     #100 $finish;
   end
endprogram : testcase



output:
time= 3: Delay= 3 Count= 2
time= 5: Delay= 5 Count= 8
time= 6: Delay= 6 Count= 1
time=10: Delay=10 Count=25
time=23: Delay=23 Count= 0
time=23: Exiting fork-join...

time=28: Delay= 5 Count= 3

$finish called from file "testcase.sv", line 21.

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

task automatic show(input int   delay,
          input [7:0] count
         );

       if(delay < 5)
            #(delay) $display("time=%2t: Delay=%2d Count=%2d. Short Process Running...", $time, delay, count);
       else if(delay >= 5 && delay < 10)
            #(delay) $display("time=%2t: Delay=%2d Count=%2d. Medium Process Running...", $time, delay, count);
       else if(delay >= 10 && delay < 15)
            #(delay) $display("time=%2t: Delay=%2d Count=%2d. Long Process Running...", $time, delay, count);
       else #(delay) $display("time=%2t: Delay=%2d Count=%2d. Very long Process Running...", $time, delay, count);
   
endtask



`include "./tasks.sv"
module testcase();
   initial begin
     fork
       show (3,2);   
       show (5,8);   
       show(10,25);
       show(23,0);
       show (6,1);   
     join_any
     $display("time=%2t: Exiting fork-join...\n", $time);
     show (5, 255);   
     #100 $finish;
   end

endmodule


///////////////////////////////////////////////////////////////////////////////////////////////////

The types of fork - join 
1. fork join
2. fork join_any
3. fork join_any disablefork
4. fork join_any waitfork
5. fork join_none
6,7: fork join_none with disablefork / waitfork




 







 

 

Monday, March 24, 2014

Digital Design practice - 4

Assume Cload on an EXOR-2 gate = 108 units. (yup I purposely chose it so that stage gain will be a whole number)
Approach one: every stage has equal gain
3 stage NAND realization of EXOR-2: stage 1: Y1= A NAND B
               stage 2: 2 branches, B1= Y1 NAND A, B2= Y1 NAND B
               stage 3: out = B1 NAND B2
Logical effort of 2 input NAND = 4/3.
L.E of path = 4/3 * 2 * 4/3 * 4/3
Gain of path = Cload/ Cin * path LE = 108 * 128/27 = 4 * 2 * 4 cubed
stage gain = Nth root of path gain, where N = number of stages, aka 3 in our example.
stage gain  = 8
(Note: Optimum gain is 4, though gain of 2-6 is also OK. Probably in this case we need to use a 4th stage?)
Working gate sizes backwards: gain = LE * Cout / Cin
Cin = LE * Cout / gain
Cin3 = 4/3 * 108/ 8 = 18
Cin2 = 4/3 * 18 / 8 = 3

since there were 2 branches at stage 2, Cout1 = 2*3=6
Cin3 = 4/3 * 6/ 8 = 1 (getting Cin =1 confirms that calculations are correct)

Now, get the gate sizes: suppose unit gate capacitance corresponds to 20 lambda
stage1: PMOS 2, NMOS 2 => PMOS or NMOS W/L is 10lambda/2 lambda
stage2: PMOS 6, NMOS 6 => W/L is 30lambda/2lambda
stage3: PMOS 36, NMOS 36 => W/L is 180lambda/2lambda

delay of nand2: falling: R/2*2C + R*(6C+4fC) = RC( 4f + 7)
rising: R*(4f+8)
average delay = 4f + 7.5

stage1 f=3*2=6, stage2 f=6, stage3 f= 6
delay of the exor gate designed above = 3*(4*6 + 7.5) = 94.5


Another way to solve this is such that every stage has equal fanout.  The 3rd technique to solve this is such that every stage has equal delay. In this case all 3 methods give same answer because all 3 stages are the same.
I will pick a different value of Cload for some extra practice.
Let Cload = 81.
Method2: equal fanout: F = cuberoot (81*2) = 5.45;
Cin2 = 5.45 * Cin1 /2 = 2.725
Cin3 = 2.725 * 5.45 = 14.85
Cload= 14.85 * 5.45 = 81 confirmed, and verified calculations.
delay = 3 (4f + 7.5)  = 3(4*5.45 + 7.5) = 87.9
Equal fanout method is very convenient to do quick mental calculations.
gate sizes: stage1: PMOS, NMOS W/L = 10 lambda/2 lambda
stage2: PMOS, NMOS W/L = 27.256 lambda/ 2lambda
stage3: PMOS, NMOS W/L= 148.586 lambda

I will pick a different combination for more practice. Lets do an OR gate with Cload = 15.
Method1: equal stage gain
NOR-2 g= 5/3, INV g=1, path LE 5/3 * 1 = 5/3
path gain = path LE * Cout / Cin = 5/3*15=25
stage gain = root (25) = 5
Cout/Cin * stage_LE = stage_gain
Cin = LE * Cout / stage_gain
Cin for inverter = 1 * 15/ 5 = 3.
Gtae sizing assuming unit capacitance = gate capacitance for 20 lambda
NOR-2: PMOS=16/2, NMOS =4/2
INV: PMOS = 40/2, NMOS = 20/2
delay of NOR2: rising: 2R/4*4C + 2R/2(4C + 4C + 2C + 5hC) = RC (12 + 5h)
falling: R(4C + 2C + 5h) = RC(6+5h)
average = RC(5h + 9)
stage1 f = 3/1 = 3, stage2 f = 15/3 = 5
delay = (5*3 + 9) + (3*5 + 3) = 42

Method2: equal fanout, f = root(15) = 3.87
Gate sizing:
NOR-2: PMOS 16/2, NMOS = 4/2
Cin2 = 3.87= 77.4 lambda cap, INV: PMOS=51.6/2, NMOS=25.8/2 => obviously we have to choose whole number sizing, PMOS = 52/2, NMOS=26/2
delay = 8*3.87 + 12 = 42.96

Method3: equal stage delay:
5h1+9=3h2+3, where h1h2=15
h2=(5h1+6)/3
h1*(5h1+6)=45
5h1square + 6h1 -45 =0,
roots = {-6 +/- root (36+4*5*45)}/10
h1= 2.46
delay = 2( 5*2.46 + 9) = 42.6
Note that all the 3 methods give very close results.
Also note that delay is least when using equal stage_gain.

Just for comparison, lets do OR using INV followed by NAND-2.
path LE = 1 * 4/3 = 4/3
path gain = 20
stage gain = root(20)=4.47
Cin2= 4/3*15/4.47=4.47
stage f2= 15/4.47 = 3.35, f1=4.47
delay = (3*4.47 + 3) * (4/3 * 3.35 + 9) = 30
Wow, the INV-NAND2 or gate is so much faster than NOR2-INV or gate.







   

Saturday, March 22, 2014

Digital Design Practice - 3

So I was asked about sizing of NAND gates. I gave the regular textbook answer of PMOS = size 2, NMOS = 2. Then I was asked if I could use other sizes. I said yes I could downsize what I want faster and upsize the other to keep total resistance the same.
For instance I can choose NMOS sizes as 4R/3 and 4R. The gate with size 4R/3 will be much faster.

There's a small derivation for calculating gate size for least average delay. Here it goes:
For inverter driving other inverter
For NMOS: assume size X, assume PMOS is Y times as big as NMOS. so PMOS size is YX.
NMOS resi = R/X, cap = XC
PMOS resi = 2R/XY, cap=XYC
falling delay = R/X(XC + XC + XYC + XYC) = 2(1+Y)RC
rising delay = 4(1+Y)RC/Y
2 * t-average = 2RC (3+ Y + 2/Y)
to get minimum t-average we have to find derivative w.r.t Y
1-2/Ysquare = 0
Y = root-2

unit width transistor = 6 lambda (contact size 2, surrounded by 1 diffusion for diff-contact connection, surrounded by 1 lambda because if you want to have twice unit width and all, there is minimum spacing you need between 2 metals.) For unit average resistance:
6R/X + 12 R/root2*X = 2R
X = 3*root2 + 3 = 7.24 lambda

So for least average delay, inverter sizing is NMOS = 7.24 lambda, PMOS = root2 * 7.24 lambda = 10.24 lambda.

I was curious if this root2 sizing holds good for other gates too? and also for different driver-load combinations?
Lets choose a inverter driving NAND.
2 inputs of NAND are shorted together driven by INv, so its gate cap at inverter load = 2XC + 2XYC.
2* t-average = R/X ( XC + XYC + 2XC + 2XYC) + 2RC/XY (3XC + 3XYC)
2* t-average = 3RC (1+Y + 2/Y + 2)
2*t-average = 3RC(3 + Y + 2/Y)
Surprise surprise!! the root2 sizing holds good for this case too!!
So basically this Y + 2/Y part that contributes to derivative is coming from the mobility ratio which I assumed as 2.
So in general if NMOS mobility is n times PMOS mobility, for least average delay the PMOS is root-p times NMOS.

Case 2: Least sizing for NAND2
Temporarily taking X out of equation.
NMOS size = 2, PMOS size = Y
Assume NAND driving inverter.
Bottommost nmos sees:  2C + 2C + YC + YC + YC + C capacitance on its path to Y.
upper nmos sees:  3C + 3YC cap.
elmore delay when nmos on: RC/2(5+3Y+3+3Y) = RC(4+3Y)
each pmos sees: YC + YC + 2C + YC + C
elmore delay when one of the pmos on = 2RC/Y(3Y + 3)
2*t-average = RC(4+3Y + 6 + 6/Y)
3 -6/Ysquare = 0
Y = root2
PMOS size = root2, NMOS =2

Sometimes, elmore delay is also calculated in terms of how much resi. a capacitor sees. Same thing effectively.
rising delay when only 1 pmos on: 2R/Y( 3C+3YC) = RC(6/Y + 6)
falling delay when both nmos on: R/2*2C + R*(3C+3YC) = RC (4 + 3Y)

Case 3: Least sizing for NOR2 driving inverter
NMOS size = 1, PMOS size = 2Y
Topmost pmos sees: (2Y + 2Y + 2 + Y + 1)C = (5Y + 3)C
lower pmos sees: (3Y + 3)C
elmore delay when both pmos on: 2RC/2Y*(5Y+3) + 2RC/2Y*(3Y+3)  = RC(8+6/Y)
each nmos sees: (3+ 3Y)C
elmore delay when one of the nmos is on: RC(3+3Y)
2*t-average = RC(8 + 6/Y + 3 + 3Y)
-6/Ysquare+3 = 0
Y = root2
PMOS size = 2root2, NMOS size=1

It is quite impressive that Y came out to be root2 for gates when inverter was considered as the load.

Case 4: NOR3 driving F*NOR3
NMOS size=1, PMOS size = 3Y
pmos on: 2RC/3Y(9Y + 3 + 9FY + 3F) + 2RC/3Y(6Y+3 + 9FY + 3F) + 2RC/3Y(3Y+3 + 9FY +3F) =2RC/3Y(18Y +9+27FY + 9F)
=RC/(12+6/Y+18F+6F/Y)
one of the nmos on: RC(3Y+3 + 9FY + 3F)
2*t-average = RC(12 + 6/Y + 18F + 6F/Y + 3Y + 3 + 9FY + 3F)
 -(6F+6)/Ysquare + (3+9F) =0
Y=root {(6F+6)/(9F+3)}




Friday, March 21, 2014

Digital Design Practice - 2

Counter from D flip flop:
Up
Down
Q2
Q1
Q0
Q2+
Q1+
Q0+
1
0
0
0
0
0
0
1
1
0
0
0
1
0
1
0
1
0
0
1
0
0
1
1
1
0
0
1
1
1
0
0
1
0
1
0
0
1
0
1
1
0
1
0
1
1
1
0
1
0
1
1
0
1
1
1
1
0
1
1
1
0
0
0
K maps for up:
D0 = Q0bar;
D1 = Q0 exor Q1;
D2 = Q2 exor Q1Q0

extending the logic: 4 bit counter
Q3
Q2
Q1
Q0
Q3+
Q2+
Q1+
Q0+
0
0
0
0
0
0
0
1
0
0
0
1
0
0
1
0
0
0
1
0
0
0
1
1
0
0
1
1
0
1
0
0
0
1
0
0
0
1
0
1
0
1
0
1
0
1
1
0
0
1
1
0
0
1
1
1
0
1
1
1
1
0
0
0

D3 = Q3.(Q2.Q1.Q0) + Q3bar((Q2Q1Q0)bar)
hence D3 = Q3 exor Q2Q1Q0

Did you start noticing the trend here? In fact even D0 = Q0 exor 1 to keep in line with the trend.  

Now lets add a count enable to this curry. 
D0 = Q0 exor en
D1 = Q1 exor Q0.en
D2 = Q2 exor Q1.Q0.en
D3= Q3 exor Q2.Q1.Q0.en

And now with the "en" in place it is so easy to obtain a 8 bit counter from 2 4-bit counters. 
Lower set of 4-bit counters follow above equation. 
Upper set follow same equation, simply replace en by Q3Q2Q1Q0en. 
Now if you notice this, the equation for D7 is Q7 exor Q6Q5Q4Q3Q2Q1Q0en. I dont like this ripple chain already. But my interview question was how does 128 bit counter schematic look like. My answer was to use a ripple counter with T flip flops. But the interviewer insisted that it be a synchronous counter, and he pointed out that T flip flops usually arent in the standard cell library. What the interviewer drew for me was a set of 128 flip flops which go to a 128-bit adder with other input to adder as 1. I am hoping the adder doesnt have a ripple carry :P My interview question was how can I save area in the implementation that the interviewer drew for me. I said either get equations for all D's and do it by eqations (that time I didnt know about this ripple chain) or use pipelining to reuse a 8 bit adder or whatever. 

I am going to count the gates for above circuit for 8 bit counter: 
Exor = 5 NAND gates = 4 exors=20 nand. 
Ands: 2/3/4 input nands + inverters.
upper set enable generation: and = 5 input nand + inverters
total transistors for the above implementation of 8 bit counter = 2(80+24) + 12 = 220 + D flip flops. 

For curiosity's sake I synthesized a 8 bit counter using Cadence RC. It was too complicated to post here, so downgraded to 4-bit counter for demo. Here it goes:
module counter(clk, reset, en, outp);
  input clk, reset, en;
  output [3:0] outp;
  wire clk, reset, en;
  wire [3:0] outp;
  wire UNCONNECTED, UNCONNECTED0, UNCONNECTED1, UNCONNECTED2, n_0, n_1,
       n_2, n_3;
  wire n_4, n_5, n_6, n_7, n_8, n_9, n_10, n_11;
  wire n_12;
  SFF \outp_reg[2] (.RD (reset), .CK (clk), .D (n_12), .SI
       (n_11), .SE (outp[2]), .Q (outp[2]), .SO (UNCONNECTED));
  SFF \outp_reg[3] (.RD (reset), .CK (clk), .D (n_0), .SI
       (outp[3]), .SE (n_10), .Q (outp[3]), .SO (UNCONNECTED0));
  SFF \outp_reg[1] (.RD (reset), .CK (clk), .D (n_7), .SI
       (n_6), .SE (outp[1]), .Q (outp[1]), .SO (UNCONNECTED1));
  INV g518(.A (n_11), .X (n_12));
  ND2 g517(.A1 (n_5), .A2 (n_8), .X (n_10));
  ND2 g519(.A1 (n_9), .A2 (outp[0]), .X (n_11));
  SFF \outp_reg[0] (.RD (reset), .CK (clk), .D (outp[0]),
       .SI (n_1), .SE (en), .Q (outp[0]), .SO (UNCONNECTED2));
  INV g524(.A (n_3), .X (n_9));
  INV g526(.A (n_2), .X (n_8));
  INV g520(.A (n_6), .X (n_7));
  INV g522(.A (n_4), .X (n_5));
  ND2 g523(.A1 (outp[0]), .A2 (outp[1]), .X (n_4));
  ND2 g525(.A1 (outp[1]), .A2 (en), .X (n_3));
  ND2 g527(.A1 (outp[2]), .A2 (en), .X (n_2));
  ND2 g521(.A1 (outp[0]), .A2 (en), .X (n_6));
  INV g529(.A (outp[0]), .X (n_1));
  INV g528(.A (outp[3]), .X (n_0));
endmodule

(Note: it used equation: Q = D*SEbar + SI*SE + D*SI). Quite ingenious the way it saved exor gates using scan flip flops.  8-bit counter was synthesized in the same way. Impressed!! but still think that what if there really was a scan chain that needed to be used. What would the circuit be synthesized to in that case!. For now I will stop here till I have enough patience to use DFT flow etc.

P.S: Technology schematic on Xilinx used look up tables. Note we should take the xilinx equations with a pinch of salt because a) if we are designing an asic from scratch, our equations are going to be different => either reduced or not reduced to avoid glitches, and b) in most circumstances we would not be using look up tables anyways if we are not using FPGAs.
D0 = Q0bar.  Qo -> inverter -> inverter output goes to D0
D1 = Q1 exor Q0
D2 = used a look up table with equation: D2 = ((Q0 * !Q2 * Q1) + (!Q0 * Q2) + (Q2 * !Q1)); 
D3 used a look up table with equation: D3 = ((Q3 * !Q1) + (Q0 * !Q3 * Q1 * Q2) + (!Q0 * Q3) + (Q3 * !Q2)); 

P.S 2) If we are using T flip flops to get a asynchronous 128 bit counter its going to need a gigantic verification effort. At each stage, the Q will toggle after "clock reaches previous stage + clock to Q delay of previous stage". So net delay from main clock toggles to FFx toggles is X*clock_to_Q_delay. Forget about 128 bit counter, we could have likely messed up things by 16 bit counter itself.

P.S 3) During my interview, I had said that ideally only least significant few bits are going to be toggling all the time. So maybe we can do something to use this to our advantage. That time I had said maybe there is another adder already lying around (something on the lines of Tomasulo's algorithm). So my best answer is: we have only a 8 bit counter (or whatever sounds reasonable, 16 bit or 32 bit, I definitely havent heard of a 128 bit microprocessor yet). Kind of have something as a mini Interrupt to signal that 8 bit counter overflew. Then store the rest of the count in registers and use the adder in ALU to increment the register. This way you are using the adder that is already present and saving on the area.