//Program /home/stebla/programs/scilab/method-of-moments-v5.sci //to solve the scalar Helmholtz equation for the E-field on a pcb //in the presence of decoupling capacitors. Lines ending in ? //can be editted to change the program parameters. // //References //[1] "The impedance of a pcb with discrete decoupling capacitors //calculated using the method of moments of R.F. Harrington", 4th //March 2007, Works Volume XXII. //History //6th Mar 2007: Created. //7th Mar 2007: Copied the original method-of-moments.sci to //method-of-moments-v2.sci because the original had a fundamental //error which calculated the low frequency impedance as inductive, //which is unphysical. This version solves the new integral equation //in section 11 of reference [1]. //8th Mar 2007: Copied the method-of-moments-v2.sci to //method-of-moments-v3.sci because the v2 variant does not have the //correct behaviour at low k. The v3 variant uses Scilab's canned //integration routine intg to calculate the matrix elements move exactly, //in the hope that this will fix the problem at low k - it does! //8th Mar 2007: Copied the method-of-moments-v3.sci to //method-of-moments-v4.sci . The v3 variant has the correct behaviour //at low k, but it achieves this by doing the "boundary field due to //boundary field" integral using Scilab's canned integration routine //intg; this is slow, and we don't need this precision at higher k //when the Hankel function is better-behaved. Consequently, the v4 //variant has a k cut-off; below the cut-off we use intg, above the //cut-off we use the crude approximation in the v2 variant. //8th Mar 2007: method-of-moments-v4-inst1.sci adds the ability //to set up port coords to put several species of capacitors on a //rectangular pcb. //4th Feb 2008: Tidied up the code to make one canonical version which //can be put on cvs and so all the previous versions can be forgotten. // The new version is method-of-moments-v5.sci . //Usage //-->exec('method-of-moments-v5.sci'); //-->plot2d(rvec,image(Ezvec));//Plot calculated E-field versus radius //clear(); //Program controls circular_board=1;//Circular board shape - do not edit rectangular_board=2;//Rectangular board shape - do not edit arbitrary_board=3;//Arbitrary board shape - do not edit board_shape=rectangular_board;//Generate vertices automatically for a circular board ? if board_shape==circular_board then r1=100.0e-3;//radius for a circular board ? N=16;//No of boundary edges ? elseif board_shape==rectangular_board then a=100.0e-3;//length for a rectangular board ? b=100.0e-3;//width for a rectangular board ? Nx=5;//Segments along x edge for rect brd ? Ny=5;//Segments along y edge for rect brd ? else // N=20;//No of boundary vertices for an arbitrary board ? end; er=4.2;//Relative permittivity of board dielectric ? r0=125.0e-6;//Radius of the via-posts ? h=100.0e-6;//Separation of the planes ? plot_boundary_and_ports=%T;// ? board_display=[-0.1,-0.1,0.1,0.1];//board display rect [xmin,ymin,xmax,ymax] ? calc_field=%F;// ? f=1000.0e6;// Fixed frequency for E-field calc ? if board_shape==circular_board then rvec=r0:0.01*r1:r1;//Radial points to calculate E-field solution ? else rvec=r0:0.001:0.1;//Radial points to calculate E-field solution ? end; calc_impedance=%T;// ? f0=100.0e3;// Start frequency for impedance calc? no_of_decades=5;//No of decades f0 to f0*10^no_of_decades ? no_of_data_points=100;//No of data points for impedance calc ? plot_bare_board_impedance=%T;// ? plot_power_plane_impedance=%T;// ? fcutoff=50.0e9;//Use intg below this frequency ? // ************** Decoupling caps ? ************** //These arrays specify the decoupling caps by species. The array sn is the //number of caps in each species on the board. C is the cap value for each species, //Lesl is the effective series inductance for each species and Resr is the effective //series resistance for each species. These values are just used in the argument //list of the function standard_cap to calculate the discrete impedance Zd. //If the model for the TDK MLCC caps is required, the if statement in the code //has to be modified to call the mlcc function instead of standard_cap, because //the MLCC model uses extra parameters. sn= [1 ,3 ,3 ,0 ,37 ,0 ,0 ,0 ,0 ]; C= [470.0e-6,10.0e-6,1.0e-6 ,330.0e-9,100.0e-9,33.0e-9 ,10.0e-9 ,3.3e-9 ,1.0e-9 ]; Lesl=[4.0e-9 ,1.5e-9 ,1.5e-9 ,1.5e-9 ,1.5e-9 ,1.5e-9 ,1.5e-9 ,1.5e-9 ,1.5e-9 ]; Resr=[19.0e-3 ,20.0e-3,20.0e-3,40.0e-3 ,60.0e-3 ,130.0e-3,200.0e-3,300.0e-3,400.0e-3]; //*********************************************** function v=vertices_for_circular_board(N,r1) //Returns the N vertices for a circular board of radius r1. v=zeros(N,2);//Boundary vertices for i=1:N, phi=(i-1)*2*%pi/N; v(i,:)=[r1*cos(phi),r1*sin(phi)]; end; endfunction; function [v,N]=vertices_for_rectangular_board(Nx,Ny,a,b) //Returns the N vertices for a rectangular board of size a x b //with Nx segments along x and Ny segments along y. N=2*(Nx+Ny); da=a/Nx; db=b/Ny; v=zeros(N,2);//Boundary vertices for i=1:Nx, v(i,:)=[(i-1)*da-0.5*a,-0.5*b]; end; for j=1:Ny, v(Nx+j,:)=[0.5*a,(j-1)*db-0.5*b]; end; for i=1:Nx, v(Nx+Ny+i,:)=[0.5*a-(i-1)*da,0.5*b]; end; for j=1:Nx, v(2*Nx+Ny+j,:)=[-0.5*a,0.5*b-(j-1)*db]; end; endfunction; function pe=random_ports_for_circular_board(M,r1) //A function to automatically generate M ports uniformly //over a circular board of radius r1. pe=zeros(M,2); pe(1,:)=1.0e-3*[0,0];//Driver port coords at centre for i=2:M, u=rand();//Two random number on [0,1] v=rand(); r=r1*sqrt(u); theta=2*%pi*v; pe(i,:)=[r*cos(theta),r*sin(theta)]; end; endfunction; function pe=random_ports_for_rectangular_board(M,a,b) //A function to automatically generate M ports uniformly //over a rectangular board of dimensions a x b. pe=zeros(M,2); pe(1,:)=1.0e-3*[0,0];//Driver port coords at centre for i=2:M, u=rand();//Two random number on [0,1] v=rand(); x=a*(u-0.5); y=b*(v-0.5); pe(i,:)=[x,y]; end; endfunction; function pe=hand_crafted_ports(M) // A function to encapsulate the edits for hand-crafted //ports. Clearly M must agree with the vertices //actually assigned. pe=zeros(M,2); pe(1,:)=1.0e-3*[0,0];//Driver port coords pe(2,:)=1.0e-3*[-15,-30]; pe(3,:)=1.0e-3*[-15,-35]; pe(4,:)=1.0e-3*[-10,-40]; pe(5,:)=1.0e-3*[-10,-30]; pe(6,:)=1.0e-3*[-10,-35]; pe(7,:)=1.0e-3*[-5,-40]; pe(8,:)=1.0e-3*[-5,-35]; pe(9,:)=1.0e-3*[-5,-30]; endfunction; function v=hand_crafted_vertices(N) // A function to encapsulate the edits for hand-crafted //vertices. Clearly N must agree with the vertices //actually assigned. v=zeros(N,2);//Boundary vertices v(1,:)=1.0e-3*[-50,-50]; v(2,:)=1.0e-3*[-30,-50]; v(3,:)=1.0e-3*[-10,-50]; v(4,:)=1.0e-3*[10,-50]; v(5,:)=1.0e-3*[30,-50]; v(6,:)=1.0e-3*[50,-50]; v(7,:)=1.0e-3*[50,-30]; v(8,:)=1.0e-3*[50,-10]; v(9,:)=1.0e-3*[50,10]; v(10,:)=1.0e-3*[50,30]; v(11,:)=1.0e-3*[50,50]; v(12,:)=1.0e-3*[30,50]; v(13,:)=1.0e-3*[10,50]; v(14,:)=1.0e-3*[-10,50]; v(15,:)=1.0e-3*[-30,50]; v(16,:)=1.0e-3*[-50,50]; v(17,:)=1.0e-3*[-50,30]; v(18,:)=1.0e-3*[-50,10]; v(19,:)=1.0e-3*[-50,-10]; v(20,:)=1.0e-3*[-50,-30]; endfunction; function u=direction(p1,p2) //unit vector from p2 to p1; u=(p1-p2)/|p1-p2| u=p1-p2; len=sqrt(u*u'); u=u/len; endfunction; function d=distance(p1,p2) //distance from p2 to p1; d=|p1-p2| u=p1-p2; d=sqrt(u*u'); endfunction; function D1_kernel=D1_int(t,k,p,dl,e,p0) //Function for integral over a boundary segment. //The midpoint of the segment is p, the segment edge //vector is dl, the outward unit normal to the segment //is e, the field point is p0. This function must not //be used when the field point p0 taken on the segment //itself, that case has to be done analytically because //of the singularity in the Hankel function. The path is //parameterised by t which goes from -1/2 to 1/2. The //wavenumber is k. Scilab's intg canned integration //routine only seems to do real integrals, so we have to //split the integral up into real and imaginary parts. //This does the real part. pt=p+t*dl; d=distance(pt,p0); u=direction(pt,p0); dircos=e*u'; H21kd=besselh(1,2,k*d); D1_kernel=real(H21kd*dircos); endfunction; function D2_kernel=D2_int(t,k,p,dl,e,p0) //Function for integral over a boundary segment. //The midpoint of the segment is p, the segment edge //vector is dl, the outward unit normal to the segment //is e, the field point is p0. This function must not //be used when the field point p0 taken on the segment //itself, that case has to be done analytically because //of the singularity in the Hankel function. The path is //parameterised by t which goes from -1/2 to 1/2. The //wavenumber is k. Scilab's intg canned integration //routine only seems to do real integrals, so we have to //split the integral up into real and imaginary parts. //This does the imaginary part. pt=p+t*dl; d=distance(pt,p0); u=direction(pt,p0); dircos=e*u'; H21kd=besselh(1,2,k*d); D2_kernel=imag(H21kd*dircos); endfunction; function [CNxM,DNxN,CMxM,DMxN]=port_matrices(k) //Calculates the port matrices for a given wavenumber k //Assumes that the geometry has been set up in the //arrays p,pe,e,dl. ea=1.0e-10;//abolute error for intg er=1.0e-3;//realtive error for intg //Construct the CNxM matrix (boundary fields due to port currents) CNxM=zeros(N,M); for i=1:N, for l=1:M, H20kd=besselh(0,2,k*distance(p(i,:),pe(l,:))); CNxM(i,l)=-0.25*k*zeta0*H20kd; end; end; //Construct the DNxN matrix (boundary fields due to boundary fields) DNxN=zeros(N,N); for i=1:N, for j=1:N, if i==j then DNxN(j,j)=0.5; else Dl=sqrt(dl(j,:)*dl(j,:)');//Segment length if k