#!/bin/sh
#
# $Id: cascade.sh,v 1.2 2001/09/05 17:52:47 dan Exp $
#
#
# Cascade
#
# A program for performing noise figure, gain, and third order intercept
# analysis of a cascade.
#
# 
# Copyright (c) 1997-2001 Dan McMahill <mcmahill@alum.mit.edu>
# All rights reserved.
#
# This code is derived from software written by Dan McMahill
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#        This product includes software developed by Dan McMahill
#  4. The name of the author may not be used to endorse or promote products
#     derived from this software without specific prior written permission.
# 
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
#  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
#  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
#  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
#  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#  SUCH DAMAGE.
# 

AWK=awk

#
# This program was developed using Gnu Awk (gawk) 2.15, patchlevel 6
# running on a MacIntosh IIci under NetBSD/mac68k 1.2.1
# It was later extended under NetBSD/mac68k 1.3 still using gawk
# 2.15 patchlevel 6.
# 
# It should work with any version of gawk that is at least 2.15, and
# at least with some versions of nawk (I have only tested it with nawk
# on SunOs4.1.4 and Solaris2.5.1).  It will not work with a more
# traditional awk.
#


##############################
# START OF PROGRAM
##############################

PROGNAME="cascade"
VERSIONNUM="1.4"

##################
# Check for help request and/or version request
##################

if test "$1" != ""
then
    case "$1" in
	--copyright)
	    echo " "
	    echo "Copyright 1997-2001, Dan McMahill" 
	    echo " "
	    echo "Cascade is freely available under a BSD-style copyright"
	    echo "Please refer to the file COPYING which should have come"
	    echo "with your copy of cascade."
	    echo " "
	    exit 0 ;;

	--help) 
	    echo ""
	    echo "USAGE:  $0 --option"
	    echo "        $0 infilename"
 	    echo "        $0 infilename > outfile "
	    echo ""
	    echo "Available options are:"
	    echo "  --copyright     Displays copyright information"
	    echo "  --help          Displays this help message"
	    echo "  --version       Displays version information for"\
			"the program"
	    echo ""
	    echo "$0 inputfile"
	    echo "  reads a description of a system level cascade from"\
			"the text inputfile"
	    echo "  and computes noise figure, third order intercept,"\
			"and C/N degredation"
	    echo "  at each stage of the cascade.  The results are sent"\
			"to the standard"
	    echo "  output.  The standard output may be redirected to a"\
			"file using:"
	    echo "  $0 infilename > outfilename"
	    echo ""
	    echo "  The results can also be piped to more to keep them"\
			"from scrolling off"
	    echo "  the screen by doing:"
	    echo "  $0 infilename | more"
	    echo ""
	    echo "For more information see the HTML documentation which should"
	    echo "have been installed along with this copy of cascade."
	    echo ""
	    echo "You can also obtain online help, report bugs, check for the"
	    echo "latest versions, and participate in online discussion forums"
	    echo "at the sourceforge home page for this project."
	    echo "http://www.sourceforge.net/projects/rfcascade/"
	    echo ""
	    exit 0 ;;

	--version)
	    echo ""
	    echo "$PROGNAME $VERSIONNUM"
	    echo ""
	    exit 0 ;;

	-*) echo "$0: Unknown argument $1" 1>&2; exit 1 ;;

	*)
    esac
else
    echo ""
    echo "USAGE:  $0  [--copyright] [--help] [--version]"
    echo "        $0  infilename"
    echo ""
    exit 0;
fi

#######################################################
# Check to see if the input file exists and is readable
#######################################################
if [ ! -r $1 ] 
then
    echo ""
    echo "ERROR:  Input file $1 not found or is not readable"
    echo ""
    exit 1
else
  infile=$1
fi


#
# run the main gawk/nawk program
#
$AWK  ' 
BEGIN{ 
        InFile = ARGV[1]

	# modify the field seperator to include = as well as whitespace
	# this will make it easy to parse the elements
	FS = "[ \t=]+"
    
        printf("************************************************************\n")
	printf("*                    CASCADE ANALYSIS                      *\n")
        printf("*                     Version %5s                        *\n",\
                                                                   ARGV[2]);
	printf("*               (c) 1997-2001 Dan McMahill                 *\n")
	printf("*                  mcmahill@alum.mit.edu                   *\n")
        printf("************************************************************\n")
	print("")
	InitializeSystem()

	print("Processing input file \""InFile"\"\n")

	ProcessInputFile(InFile)
	close(InFile)

        MakeSummary()

        printf("************************************************************\n")
	printf("*              CASCADE ANALYSIS COMPLETE                   *\n")
	
	if(ARGV[0] == "gawk"){
	  now = strftime("%a %b %d %H:%M:%S %Z %Y")
	  printf("*            %28s                  *\n",now)
	}
        printf("************************************************************\n")
	print("")

	exit 0
}
function InitializeSystem(){
    # initialize variables
	# total system power gain (ratio)
	gain=1

	# reference noise temperature
	tn0=290

	# total output noise temperature
	tn=tn0

	# source noise temperature (want to  read from infile)
	tns="none"

	# distortion present at each output
	d=0

	# carrier level at each output
	C="none"
	c="none"

	# current stage of the cascade
	stage=1

	# flag that says we are still reading things like the source line
	# and the default line

	# default input/output resistances of each block
	rin  = 50;
	rout = 50;

	# The default source resistance
	Rs = rout;
	routii=rout;

	# The default rho for IIP3 calculations
	rho_default = 0;

        # index into the array which holds NF and IIP3 due to
        # each stage only (used at the end to rank each stage
        # in terms of which hurts each spec the most)
        nf_index = 1
        iip_index = 1

}

function ProcessInputFile(file){
	while(getline < file > 0){
		ProcessInputLine($0)
	}
        print("")
    printf("************************************************************\n")
    printf("****** ANALYSIS OF THE %2d ELEMENT CASCADE IS COMPLETE ******\n",stage-1)
    printf("************************************************************\n")
}

function ProcessInputLine(line){
       	if(line~/^[\t ]*[#;*]/ || line~/^[\t ]*$/){
	    # skip over comment lines and blank lines
	}
       	else if(line~/^[\t ]*@/){
	    # echo @ style comment lines to the output
	    print $0
	}
	else{
	    ParseLine(line);
	}
}

function ParseLine(line){

    StageName=$1

    if(StageName~/[Ss][Oo][Uu][Rr][Cc][Ee]/){
	if(stage != 1){
	    print("WARNING:  Source line was ignored")
	    print("  \""$0"\"")
	    print("  This source line was ignored because "\
		    "the source MUST be the FIRST")
	    print("  element in the cascade and this line was line "stage)
	    print("")
	}
	else{
	    ParseSource(line)
	}
    }
    else if(StageName~/[dD][eE][fF][aA][uU][lL][tT][sS]/){
	    ParseDefaults(line);
    }
    else{

    Gi="";
    GVi="";
    NFi="";
    IIP3i="";
    rhoi=rho_default;
    rini=rin;
    routi=rout;

    i=2
    while($i != ""){
    if($i~/[Gg][Pp]/){
	# found power gain
	i++
	Gi=$i
	}
    else if($i~/[Gg][Vv]/){
	# found voltage gain
	i++
	GVi=$i
	}
    else if($i~/[Gg]/){
	# found old format gain which is assumed to be power gain
	i++
	Gi=$i
	}
    else if($i~/[Nn][Ff]/){
	# found noise figure
	i++
	NFi=$i
	}
    else if($i~/[Ii][Ii][Pp][3]/){
	# found iip3
	i++
	IIP3i=$i
	}
    else if($i~/[Rr][Ii][Nn]/){
	# found rin
	i++
	rini=$i
	}
    else if($i~/[Rr][Oo][Uu][Tt]/){
	# found rout
	i++
	routi=$i
	}
    else if($i~/[Rr]/){
	# found rho
	i++
	rhoi=$i
	}
    else{
	print("SYNTAX ERROR:  The entry \""$i"\" in the line ")
	print("\""$0"\"")
 	print("Is not understood\n\n")
	exit 1
    }

    i++
    }

    if((Gi=="") && (GVi == "")){
	print("ERROR:  Gain must be specified")
	print("Line with the error is:")
	print($0)
    }
    if((Gi!="") && (GVi != "")){
	print("ERROR:  You can only specify power gain _or_ voltage gain");
	print("        not both!");
	print("Line with the error is:")
	print($0)
    }

    if(Gi == ""){
	Gi = GVi + 10*log10(rini/routi);
    }

    if(GVi == ""){
	GVi = Gi + 10*log10(routi/rini);
    }

    if(NFi==""){
	NFi=0
    }
    if(IIP3i==""){
	IIP3i="nodist"
    }

    ComputeStage()
    PrintStageSummary()

    stage++

}

}



function ParseDefaults(line,i){

    i=2
    while($i != ""){
    if($i~/[Rr][Ii][Nn]/){
	# found rin
	i++;
	rin=$i;
	}
    else if($i~/[Rr][Oo][Uu][Tt]/){
	# found rout
	i++;
	rout=$i;
	}
    else if($i~/[Rr][Hh][Oo]/){
	# found rho
	i++;
	rho_default=$i;
	}
    else{
	print("SYNTAX ERROR:  The entry \""$i"\" in the line ")
	print("\""$0"\"");
 	print("Is not understood.  This line is supposed to be the defaults line\n\n");
	exit 1;
    }

    i++;
    }

    print("************************************************************")
    print("*    Default Values Changed                                *")
    print("************************************************************")
    print("");
    print("     Input  Resistance for each Stage = "rin " Ohms");
    print("     Output Resistance for each Stage = "rout" Ohms");
    print("     Default Rho (for IIP3 calc.)     = "rho_default);
    print("");

}

function ParseSource(line,i){

    Cs=""
    CNs=""
    BWs=""
    CNos=""
    TNs=""
    Nos=""
    
    # use the default value
    Rs=rout;
    
    need_source=0;

    i=2
    while($i != ""){
    if($i~/[Cc][Nn][Oo0]/){
	# found C/No
	i++
	CNos=$i
	need_source=1;
	}
    else if($i~/[Nn][0oO]/){
	# found noise spectral density
	i++
	Nos=$i
	need_source=1;
	}
    else if($i~/[Tt][Nn]/){
	# found input noise temperature
	i++
        TNs=$i
	need_source=1;
	}
    else if($i~/[Cc][Nn]/){
	# found carrier to noise
	i++
	CNs=$i
	need_source=1;
	}
    else if($i~/[Bb][Ww]/){
	# found carrier noise bandwidth
	i++
	BWs=$i
	need_source=1;
	}
    else if($i~/[Cc]/){
	# found the input carrier level
	i++
	Cs=$i
	}
    else if($i~/[Rr][Ss]/){
	# found the source resistance
	i++
	Rs=$i
	}
    else{
	print("SYNTAX ERROR:  The entry \""$i"\" in the line ")
	print("\""$0"\"")
 	print("Is not understood.  This line is supposed to be source\n\n")
	exit 1
    }

    i++
    }
  
    # done parsing the line, now do error checking
    if(Cs=="" && need_source){
	print("ERROR:  Input carrier level must be specified if a "\
		"source with");
	print("        noise parameters is specified");
	print("Line with the error is:");
	print($0);
	exit 1;
    }

    # If C/N is specified, we can not specify C/No, No, or Tn
    if(CNs!=""){
	if((CNos!="") || (Nos!="") || (TNs!="")){
	    print("ERROR:  Can not specify C/N and C/No or No or Tn,"\
		    " for the source.")
	    print("Line with the error is:")
	    print($0)
	    exit 1
	}
    }

    # If C/No is specified, we can not specify No, or Tn
    if(CNos!=""){
	if((Nos!="") || (TNs!="")){
	    print("ERROR:  Can not specify C/No and No or Tn for the source.")
	    print("Line with the error is:")
	    print($0)
	    exit 1
	}
    }

    # If No is specified, we can not specify Tn
    if(Nos!=""){
	if(TNs!=""){
	    print("ERROR:  Can not specify No and Tn for the source.")
	    print("Line with the error is:")
	    print($0)
	    exit 1
	}
    }

    if((CNs!="") && (BWs=="")){
	print("ERROR:  The receiver noise BW must be specified when "\
		"using C/N.")
	print("Line with the error is:")
	print($0)
	exit 1
    }

    # calculate initial values
    if(Cs != ""){
	C=Cs;
	c=dBmToW(C);
    }

    # C/N is specified, calculate C/No
    if(CNs != ""){
	CNos = CNs + RatioTodB10(BWs)
    }

    # No is specified, calculate C/No
    if(Nos != ""){
	CNos = C - Nos
	}

    # we have a C/No, so find No
    if(CNos != ""){
	NFs = (C+173) - CNos
	nfs=dB10ToRatio(NFs)
	tns = 290*nfs
	Nos = C-CNos
	if(Nos < -174){
	    print("  ** WARNING You have specified a No less than k*290"\
		"(-174 dBm/Hz) **\n")
		}
	if(BWs != ""){
	    CNs = CNos - RatioTodB10(BWs)
	    }
    }
    
    # Tn is specified
    if(TNs != ""){
	tns = TNs
	# k = 1.38e-23 Joules/Kelvin
	Nos = 10*log(TNs*1.38e-20)/log(10)
	CNos = C-Nos
	if(BWs != ""){
	    CNs = CNos - RatioTodB10(BWs)
	    }
	}

    if(CNs != ""){
	#CNos=""
    }
    
    # routii is used in finding loadings between stages i-1 (ii) and i.
    # routii is the output resistance of stage i-1.
    routii = Rs;

    print("************************************************************")
    print("*    Input Source                                          *")
    print("************************************************************")

    print("     Source Resistance   = "Rs" Ohms");
    print("");


    if(Cs !=""){
    print("     Input Carrier Level = "Cs" dBm")
    }

    if(Nos !=""){
    print("     Input Noise Density = "Nos" dBm/Hz")
    print("")
    }

    if(TNs !=""){
    print("     Input Noise Temp.   = "TNs" Kelvin")
    print("")
    }

    if(CNs !=""){
    print("     Input C/N           = "CNs" dB")
    print("     Receiver Noise BW   = "BWs/1e6" MHz")
    print("")
    }

    if(CNos !=""){
    print("     Input C/No          = "CNos" dB/Hz")
    print("")
    }


}


function ComputeStage(){

    # convert from dB to watts and ratios
    gi = dB10ToRatio(Gi);
    nfi = dB10ToRatio(NFi);
    if(iip3i != "nodist"){
	iip3i = dBmToW(IIP3i)
	}
    
    # add to total power gain
    gain = gain*gi;
    G = RatioTodB10(gain);

    # add to total noise temp
    tn=gi*(tn + (nfi-1)*tn0);

    # compute NF of the total cascade up to this point
    nf = (tn/gain)/tn0
    NFig = RatioTodB10(nf)


    # compute NF due to this stage only
    nf_single=1 + (nfi-1)/(gain/gi)
    NF_single=RatioTodB10(nf_single)
    NF_single_value_list[nf_index] = NF_single
    NF_value_list[nf_index] = NFi
    NF_name_list[nf_index] = StageName
    G_value_list[nf_index] = G - Gi
    nf_index_list[nf_index]=nf_index
    nf_index = nf_index+1

    # compute C/N degredation
    if(tns != "none"){
        # total
	loss=1 + (nf-1)*(tn0/tns)
	LOSS=RatioTodB10(loss)

        # from this stage only
	lossi=1 + ((nfi - 1)/(gain/gi))*(tn0/tns)
	LOSSi=RatioTodB10(lossi)
    }


    # add to distortion power
    if(IIP3i != "nodist"){
	cd = gain;
	oip3i = gi*iip3i;
	dm1 = d;
	d = gi*dm1 + (cd^3)/(oip3i^2) + 2*rhoi*sqrt(gi*dm1*(cd^3)/(oip3i^2));

        IIP3_single = IIP3i - (G-Gi);
        IIP3_value_list[iip_index] = IIP3_single;
        IIP3_name_list[iip_index] = StageName;
        iip_index_list[iip_index] = iip_index;
        iip_index = iip_index+1;
	}
    else{
	d = gi*d;
    }

    # compute IIP3
    if(d != 0){
	iip3=sqrt(gain/d);
	IIP3 = WTodBm(iip3);
    }
    else{
	iip3="nodist";
	IIP3="nodist";
	}

    # add to carrier level
    if(c != "none"){
	c=c*gi;
	C=WTodBm(c);
	}

}
function PrintStageSummary(){
    printf("************************************************************\n")
    printf("*    Stage #%-2d %-44s*\n",stage,sprintf("\"%s\"",StageName))
    printf("************************************************************\n")
    printf("     Power Gain=%6.2f dB, Voltage Gain=%6.2f dB\n",Gi,GVi);
    printf("     NF=%6.2f dB\n",NFi);
    print("     Input Res. = "rini" Ohms,  Output Res. = "routi" Ohms");
	
    if(IIP3i != "nodist"){
	printf("     IIP3=%6.2f dBm (%6.2f dBmV), RHO=%6.2f\n",IIP3i, 
	    IIP3i+30+10*log10(rini),rhoi);
    }
    else{
	print("     No Distortion In this Stage")
    }
    if(NFi<0){
	print("   **WARNING You have specified a NF < 0 dB ** ")
	}

    print(" ")
    printf("     Total Power   Gain                     =  %6.2f dB\n", G)
    printf("     Total Voltage Gain                     =  %6.2f dB\n", G + 10*log10(routi/Rs));
    printf("     Total Noise Figure                     =  %6.2f dB\n", NFig)
    printf("     Noise Figure from this stage only      =  %6.2f dB\n", NF_single)

    if(CNs != ""){
    print(" ")
    printf("     C/N                                    = %7.2f dB\n",CNs- LOSS)    
    if(CNos != ""){
    printf("     C/No                                   = %7.2f dB\n",CNos- LOSS)    
    }
    printf("     C/N Degradation                        = %7.3f dB\n", LOSS)    
    printf("     C/N Degradation from this stage only   = %7.3f dB\n", LOSSi)    
    }

    if((CNos != "") && (CNs == "")){
    print(" ")
    printf("     C/No                                   = %7.2f dB\n",CNos- LOSS)    
    printf("     C/No Degradation                       = %7.3f dB\n", LOSS)    
    printf("     C/No Degradation from this stage only  = %7.3f dB\n", LOSSi)    
    }

    if(IIP3 != "nodist"){
    print(" ")
    printf("     IIP3                                   = %7.2f dBm\n", IIP3)
    }

    if(IIP3i != "nodist"){
    printf("     IIP3 from this stage only              = %7.2f dBm\n", IIP3_single)
    }

    if(c != "none"){
    print(" ");
    printf("     Output Carrier Level                   = %7.2f dBm\n", C)
    printf("                                            = %7.2f dBmV\n", \
	    C+ 30 + 10*log10(rout));
    print(" ");
    printf("     Output Open Circuit Voltage            = %7.2f dBmV\n", \
	    C + 30 + 20*log10(2)+10*log10(rout));


	if(IIP3 != "nodist"){
	print(" ");
	printf("     Third Order Intermod Level             = %7.2f dBc\n", 2*(Cs-IIP3))
	}
    }

    print("")

}

function MakeSummary(){

    print("")
    print("************************************************************")
    print("*       Noise Figure Contribution Summary                  *")
    print("************************************************************")
    
    # calculate the best improvement to the system noise figure that
    # is possible by eliminating the noise from each block

    for(i=1;i<nf_index;i++){
      nfi  = dB10ToRatio(NF_value_list[i])
      gain = dB10ToRatio(G_value_list[i])
      NF_delta_list[i] = NFig - RatioTodB10(nf - ((nfi-1)/gain))
    }

    # sort the individual stage noise figures to rank them from worst to best
    # in terms of contribution to overall noise figure

    # NOTE:  this algorithm is order N^2 and should not be used elsewhere except
    #        to sort small (N<20 or so) lists.  The assumption here is that most
    #        of the systems analyzed will be relatively small and the overhead
    #        (including a larger programming effort) of quicksort or heapsort
    #        did not seem worth it.

    # iterate through each element
    for(j=2;j<nf_index;j++){
      i = j-1;
      
      # figure out where to insert it
      while((i>0) && (NF_single_value_list[nf_index_list[i]]<NF_single_value_list[j])){
        nf_index_list[i+1] = nf_index_list[i]
	i--
      }
      
      # insert it
      nf_index_list[i+1]=j

    }


    print("")
    print("     Stage          Noise Figure     Possible Noise Figure ")
    print("                    in the system          Improvement      ")
    print("---------------   ----------------   ----------------------")
    for(j=1;j<nf_index;j++){
      i = nf_index_list[j]
      printf("%-15s   %9.3f dB      %14.3f dB\n", NF_name_list[i], \
                NF_single_value_list[i] ,NF_delta_list[i])
    }



    print("")
    print("************************************************************")
    print("*             IIP3 Contribution Summary                    *")
    print("************************************************************")
    
    # sort the individual IIP3s to rank them from worst to best
    # in terms of contribution to overall IIP3

    # iterate through each element
    for(j=2;j<iip_index;j++){
      i = j-1;
      
      # figure out where to insert it
      while(i>0 && IIP3_value_list[iip_index_list[i]]<IIP3_value_list[j]){
        iip_index_list[i+1] = iip_index_list[i]
	i--
      }
      
      # insert it
      iip_index_list[i+1]=j
    }

    print(" ")
    print("     Stage         IIP3 in the system")
    print("---------------   --------------------")
    for(j=iip_index-1;j>0;j--){
      i = iip_index_list[j]
      printf("%-15s   %10.3f dBm\n", IIP3_name_list[i], \
                IIP3_value_list[i])
    }
   
    print("")

}

function log10(x){
	return(log(x)/log(10));
}

function dBmToW(pwrdb){
	return(0.001*10^(pwrdb/10));
}

function WTodBm(pwr){
	return(10*log(pwr/0.001)/log(10));
}

function dB10ToRatio(pwrdb){
	return(10^(pwrdb/10));
}
function RatioTodB10(rat){
	return(10*log(rat)/log(10));
}

' $infile $VERSIONNUM

