def test_ctle2(fz, fp1, npts, order, gbw, dtmax, err_lim): # normalize frequencies fz = fz*dtmax fp1 = fp1*dtmax gbw = gbw*dtmax # read in data my_data = np.genfromtxt(DATA_FILE, delimiter=',', skip_header=1) t_resp = my_data[:, 1] - my_data[0, 1] v_resp = my_data[:, 2] # find timestep of oversampled data tover = np.median(np.diff(t_resp)) assert np.all(np.isclose(np.diff(t_resp), tover)) print(f'tover: {tover*1e12:0.3f} ps') # build interpolator for data my_interp = interp1d(t_resp, v_resp) svec = np.linspace(0, 1, npts) # find state-space representation of the CTLE A, B, C, D = calc_ctle_abcd(fz=fz, fp1=fp1, gbw=gbw) # calculate response using spline method # the list of timesteps used is one that was found to be particularly bad for the emulator W = calc_interp_w(npts=npts, order=order) ctle = SplineLDS(A=A, B=B, C=C, D=D, W=W) x = np.zeros((A.shape[0],), dtype=float) t = 0 dtlist = [0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080] tlist = [] ylist = [] for dt in dtlist: tlist.append(t) x, y = ctle.calc_update(xo=x, inpt=my_interp(t+svec*dtmax), dt=dt) ylist.append(y) t += dt*dtmax # calculate measured values y_meas = interp_emu_res(tlist, ylist, dtmax, t_resp, order, npts) # find expected response of the CTLE num, den = calc_ctle_num_den(fz=fz, fp1=fp1, gbw=gbw) b, a, _ = cont2discrete((num, den), dt=tover/dtmax) y_expt = lfilter(b[0], a, v_resp) # uncomment to plot results # import matplotlib.pyplot as plt # plt.plot(t_resp, y_expt) # plt.plot(t_resp, y_meas) # plt.show() # calculate error rms_err = np.sqrt(np.mean((y_expt-y_meas)**2)) print('rms_err:', rms_err) assert rms_err < err_lim
def run_simulation(t_resp, v_resp): # find timestep of oversampled data tover = np.median(np.diff(t_resp)) assert np.all(np.isclose(np.diff(t_resp), tover)) print(f'tover: {tover*1e12:0.3f} ps') # CLTE1 nd1 = calc_ctle_num_den(fz=ctle1_fz * dtmax, fp1=ctle1_fp * dtmax, gbw=gbw * dtmax) b1, a1, _ = cont2discrete(nd1, dt=tover / dtmax) ctle1_out = lfilter(b1[0], a1, v_resp) # NL1 nl_vsat = calc_tanh_vsat(nl_dB, 'dB', veval=nl_veval) nl_func = lambda v: tanhsat(v, nl_vsat) nl1_out = nl_func(ctle1_out) # CTLE2 nd2 = calc_ctle_num_den(fz=ctle2_fz * dtmax, fp1=ctle2_fp * dtmax, gbw=gbw * dtmax) b2, a2, _ = cont2discrete(nd2, dt=tover / dtmax) ctle2_out = lfilter(b2[0], a2, nl1_out) # NL2 nl2_out = nl_func(ctle2_out) # CTLE3 nd3 = calc_ctle_num_den(fz=ctle3_fz * dtmax, fp1=ctle3_fp * dtmax, gbw=gbw * dtmax) b3, a3, _ = cont2discrete(nd3, dt=tover / dtmax) ctle3_out = lfilter(b3[0], a3, nl2_out) # NL3 nl3_out = nl_func(ctle3_out) return { 'ctle1_out': ctle1_out, 'nl1_out': nl1_out, 'ctle2_out': ctle2_out, 'nl2_out': nl2_out, 'ctle3_out': ctle3_out, 'nl3_out': nl3_out }
def test_ctle_interp2(fz, fp1, npts, order, gbw, dtmax, err_lim, simulator, real_type): # read in data my_data = np.genfromtxt(DATA_FILE, delimiter=',', skip_header=1) t_resp = my_data[:, 1] - my_data[0, 1] v_resp = my_data[:, 2] # find timestep of oversampled data tover = np.median(np.diff(t_resp)) assert np.all(np.isclose(np.diff(t_resp), tover)) print(f'tover: {tover*1e12:0.3f} ps') # build interpolator for input data my_interp = interp1d(t_resp, v_resp) svec = np.linspace(0, 1, npts) # generate model model = CTLEModel(fz=fz, fp1=fp1, gbw=gbw, dtmax=dtmax, module_name='model', build_dir=BUILD_DIR, clk='clk', rst='rst', real_type=real_type) model_file = model.compile_to_file(VerilogGenerator()) # create IO dictionary ios = {} for k in range(npts): ios[f'in_{k}'] = fault.RealIn ios[f'out_{k}'] = fault.RealOut # declare circuit class dut(m.Circuit): name = 'test_ctle_interp2' io = m.IO(dt=fault.RealIn, clk=m.In(m.Clock), rst=m.BitIn, **ios) # create the tester tester = MsdslTester(dut, dut.clk) # convenience functions for reading/writing splines def poke_input_spline(vals): for k in range(npts): tester.poke(getattr(dut, f'in_{k}'), vals[k]) def get_output_spline(): retval = [] for k in range(npts): retval.append(tester.get_value(getattr(dut, f'out_{k}'))) return retval # initialize poke_input_spline([0] * npts) tester.poke(dut.dt, 0) tester.poke(dut.clk, 0) tester.poke(dut.rst, 1) tester.eval() # apply reset tester.step(2) # clear reset tester.poke(dut.rst, 0) tester.step(2) # apply segments (chosen from a particularly bad case observed on an FPGA) dtlist = [ 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080, 0.960, 0.960, 0.080 ] t = 0 tlist = [] ylist = [] for dt in dtlist: tlist.append(t) poke_input_spline(my_interp(t + svec * dtmax)) tester.poke(dut.dt, dt) tester.eval() ylist.append(get_output_spline()) tester.step(2) t += dt * dtmax # run the simulation tester.compile_and_run( directory=BUILD_DIR, simulator=simulator, ext_srcs=[model_file, get_file('ctle_interp2/test_ctle_interp2.sv')], real_type=real_type, # dump_waveforms=True ) # convert measurements to arrays ylist = [np.array([pt.value for pt in seg]) for seg in ylist] # calculate measured values idx0 = np.where(t_resp >= tlist[0])[0][0] idx1 = np.where(t_resp >= tlist[-1])[0][0] t_interp = t_resp[idx0:(idx1 + 1)] y_meas = interp_emu_res(tlist, ylist, dtmax, t_interp, order, npts) # find expected response of the CTLE num, den = calc_ctle_num_den(fz=fz * dtmax, fp1=fp1 * dtmax, gbw=gbw * dtmax) b, a, _ = cont2discrete((num, den), dt=tover / dtmax) y_expt = lfilter(b[0], a, v_resp) # uncomment to plot results # import matplotlib.pyplot as plt # plt.plot(t_resp, y_expt) # plt.plot(t_interp, y_meas) # plt.plot(tlist, [elem[0] for elem in ylist], 'o') # plt.legend(['y_expt', 'y_meas']) # plt.show() # calculate error rms_err = np.sqrt(np.mean((y_expt[idx0:(idx1 + 1)] - y_meas)**2)) print('rms_err:', rms_err) assert rms_err < err_lim
def test_ctle_interp(simulator, real_type, fz=0.8e9, fp1=1.6e9, gbw=40e9, dtmax=62.5e-12, nover=2500000, n_segs=25, err_lim=5e-4): # make sure test is repeatable # the seed is chosen to make sure the curve is interesting enough np.random.seed(4) # generate model model = CTLEModel(fz=fz, fp1=fp1, gbw=gbw, dtmax=dtmax, module_name='model', build_dir=BUILD_DIR, clk='clk', rst='rst', real_type=real_type) model_file = model.compile_to_file(VerilogGenerator()) # create IO dictionary ios = {} for k in range(NPTS): ios[f'in_{k}'] = fault.RealIn ios[f'out_{k}'] = fault.RealOut # declare circuit class dut(m.Circuit): name = 'test_ctle_interp' io = m.IO( dt=fault.RealIn, clk=m.In(m.Clock), rst=m.BitIn, **ios ) # create the tester tester = MsdslTester(dut, dut.clk) # convenience functions for reading/writing splines def poke_input_spline(vals): for k in range(NPTS): tester.poke(getattr(dut, f'in_{k}'), vals[k]) def get_output_spline(): retval = [] for k in range(NPTS): retval.append(tester.get_value(getattr(dut, f'out_{k}'))) return retval # define input segments min_v, max_v = -2, +2 widths = np.random.uniform(0, 1, n_segs) times = np.concatenate(([0], np.cumsum(widths))) segs = [np.random.uniform(min_v, max_v, NPTS)] for k in range(1, n_segs): prv = make_cubic_func(*segs[k-1])(widths[k-1]) nxt = np.random.uniform(min_v, max_v, NPTS-1) segs.append(np.concatenate(([prv], nxt))) # initialize poke_input_spline([0]*NPTS) tester.poke(dut.dt, 0) tester.poke(dut.clk, 0) tester.poke(dut.rst, 1) tester.eval() # apply reset tester.step(2) # clear reset tester.poke(dut.rst, 0) tester.step(2) # apply segments meas = [] for k in range(n_segs): poke_input_spline(segs[k]) tester.poke(dut.dt, widths[k]) tester.eval() meas.append(get_output_spline()) tester.step(2) # run the simulation parameters = { 'in_range': 4.5, 'out_range': 4.5 } tester.compile_and_run( directory=BUILD_DIR, simulator=simulator, ext_srcs=[model_file, get_file('ctle_interp/test_ctle_interp.sv')], parameters=parameters, real_type=real_type, #dump_waveforms=True ) # convert measurements to arrays meas = [np.array([pt.value for pt in seg]) for seg in meas] # create oversampled time vector and split into chunks corresponding to each cubic section tvec = np.linspace(0, times[-1], nover) ivec = [np.searchsorted(tvec, time) for time in times[1:-1]] tvec = np.split(tvec, ivec) # create a flat vector of input values xvec = [make_cubic_func(*segs[k])(tvec[k]-times[k]) for k in range(n_segs)] xvec = np.concatenate(xvec) # apply CTLE dynamics to flat input vector, then split output values into chunks # corresponding to each cubic section num, den = calc_ctle_num_den(fz=fz*dtmax, fp1=fp1*dtmax, gbw=gbw*dtmax) b, a, _ = cont2discrete((num, den), dt=times[-1]/(nover-1)) yvec = lfilter(b[0], a, xvec) yvec = np.split(yvec, ivec) # evaluate the error for the chunk corresponding to each cubic section errs = [] for k in range(n_segs): svec = np.arange(0, widths[k], 1/(NPTS-1)) expt = interp1d(tvec[k]-times[k], yvec[k], bounds_error=False, fill_value='extrapolate')(svec) errs.append(meas[k][:len(expt)]-expt) # compute maximum error max_err = max([np.max(np.abs(elem)) for elem in errs]) print(f'max_err: {max_err}') # make sure the worst-case error is within tolerance assert max_err < err_lim
def test_ctle(fz, fp1, npts, order=3, gbw=40e9, dtmax=62.5e-12, nover=100000, err_lim=1e-4): # normalize frequencies fz = fz * dtmax fp1 = fp1 * dtmax gbw = gbw * dtmax # calculate system representation num, den = calc_ctle_num_den(fz=fz, fp1=fp1, gbw=gbw) A, B, C, D = calc_ctle_abcd(fz=fz, fp1=fp1, gbw=gbw) # define input segments seg1 = make_cubic_func(-2, 0, -0.25, 1.75) seg2 = make_cubic_func(1.75, 0, 0.1, -0.3) seg3 = make_cubic_func(-0.3, -0.1, -0.1, 1.25) # calculate response using conventional method tvec = np.linspace(0, 1, nover) xvec = np.concatenate((seg1(tvec[:-1]), seg2(tvec[:-1]), seg3(tvec))) b, a, _ = cont2discrete((num, den), dt=1 / (nover - 1)) y_expt = lfilter(b[0], a, xvec) # calculate response using spline method svec = np.linspace(0, 1, npts) W = calc_interp_w(npts=npts, order=order) ctle = SplineLDS(A=A, B=B, C=C, D=D, W=W) x = np.zeros((A.shape[0], ), dtype=float) x, y1_meas = ctle.calc_update(xo=x, inpt=seg1(svec), dt=1) x, y2_meas = ctle.calc_update(xo=x, inpt=seg2(svec), dt=1) x, y3_meas = ctle.calc_update(xo=x, inpt=seg3(svec), dt=1) # check the output a certain specific points y1_expt = y_expt[(0 * (nover - 1)):((1 * nover) - 0)] y2_expt = y_expt[(1 * (nover - 1)):((2 * nover) - 1)] y3_expt = y_expt[(2 * (nover - 1)):((3 * nover) - 2)] # sanity check to make sure slices are OK assert len(y1_expt) == nover assert len(y2_expt) == nover assert len(y3_expt) == nover # # uncomment for debugging # import matplotlib.pyplot as plt # plt.plot(tvec, y1_expt, '-') # plt.plot(svec, y1_meas, 'o') # plt.show() # plt.plot(tvec, y2_expt, '-') # plt.plot(svec, y2_meas, 'o') # plt.show() # plt.plot(tvec, y3_expt, '-') # plt.plot(svec, y3_meas, 'o') # plt.show() # sample output of conventional method y1_expt_i = interp1d(tvec, y1_expt)(svec) y2_expt_i = interp1d(tvec, y2_expt)(svec) y3_expt_i = interp1d(tvec, y3_expt)(svec) # run comparisons assert np.max(np.abs(y1_meas - y1_expt_i)) < err_lim assert np.max(np.abs(y2_meas - y2_expt_i)) < err_lim assert np.max(np.abs(y3_meas - y3_expt_i)) < err_lim
def __init__(self, fz, fp1, fp2=None, gbw=None, **kwargs): # calculate the transfer function representation num, den = calc_ctle_num_den(fz=fz, fp1=fp1, fp2=fp2, gbw=gbw) # call the super constructor super().__init__(num=num, den=den, **kwargs)