#!/usr/bin/env python3 import os, sys # PLL automatic fuzzing script (WIP) device = "up5k" # PLL config bits to be fuzzed # These must be in an order such that a config with bit i set doesn't set any other undiscovered bits yet # e.g. PLL_TYPE must be fuzzed first as these will need to be set later on by virtue of enabling the PLL fuzz_bits = [ "PLLTYPE_1", "PLLTYPE_2", "PLLTYPE_0", #NB: as per the rule above this comes later is it can only be set by also setting 1 or 2 "FEEDBACK_PATH_0", "FEEDBACK_PATH_1", "FEEDBACK_PATH_2", "PLLOUT_SELECT_A_0", "PLLOUT_SELECT_A_1", "PLLOUT_SELECT_B_0", "PLLOUT_SELECT_B_1", "SHIFTREG_DIV_MODE", "FDA_FEEDBACK_0", "FDA_FEEDBACK_1", "FDA_FEEDBACK_2", "FDA_FEEDBACK_3", "FDA_RELATIVE_0", "FDA_RELATIVE_1", "FDA_RELATIVE_2", "FDA_RELATIVE_3", "DIVR_0", "DIVR_1", "DIVR_2", "DIVR_3", "DIVF_0", "DIVF_1", "DIVF_2", "DIVF_3", "DIVF_4", "DIVF_5", "DIVF_6", #DIVQ_0 is missing, see comments later on "DIVQ_1", "DIVQ_2", "FILTER_RANGE_0", "FILTER_RANGE_1", "FILTER_RANGE_2", "TEST_MODE", "DELAY_ADJMODE_FB", #these come at the end in case they set FDA_RELATIVE?? "DELAY_ADJMODE_REL" ] # Boilerplate code based on the icefuzz script code_prefix = """ module top(packagepin, a, b, w, x, y, z, extfeedback, bypass, resetb, lock, latchinputvalue, sdi, sdo, sclk, dynamicdelay_0, dynamicdelay_1, dynamicdelay_2, dynamicdelay_3, dynamicdelay_4, dynamicdelay_5, dynamicdelay_6, dynamicdelay_7); input packagepin; input a; input b; output w; output x; output reg y; output reg z; input extfeedback; input bypass; input resetb; output lock; input latchinputvalue; input sdi; output sdo; input sclk; wire plloutcorea; wire plloutcoreb; wire plloutglobala; wire plloutglobalb; assign w = plloutcorea ^ a; assign x = plloutcoreb ^ b; always @(posedge plloutglobala) y <= a; always @(posedge plloutglobalb) z <= b; input dynamicdelay_0; input dynamicdelay_1; input dynamicdelay_2; input dynamicdelay_3; input dynamicdelay_4; input dynamicdelay_5; input dynamicdelay_6; input dynamicdelay_7; """ def get_param_value(param_name, param_size, fuzz_bit): param = str(param_size) + "'b"; for i in range(param_size - 1, -1, -1): if fuzz_bit == param_name + "_" + str(i): param += '1' else: param += '0' return param def inst_pll(fuzz_bit): pll_type = "SB_PLL40_2F_PAD" #default to this as it's most flexible if fuzz_bit == "PLLTYPE_0": pll_type = "SB_PLL40_CORE" elif fuzz_bit == "PLLTYPE_1": pll_type = "SB_PLL40_PAD" elif fuzz_bit == "PLLTYPE_2": pll_type = "SB_PLL40_2_PAD" v = pll_type + " pll_inst (\n" if pll_type == "SB_PLL40_CORE": v += "\t.REFERENCECLK(referenceclk), \n" else: v += "\t.PACKAGEPIN(packagepin), \n" v += "\t.RESETB(resetb),\n" v += "\t.BYPASS(bypass),\n" v += "\t.EXTFEEDBACK(extfeedback),\n" v += "\t.LOCK(lock),\n" v += "\t.LATCHINPUTVALUE(latchinputvalue),\n" v += "\t.SDI(sdi),\n" v += "\t.SDO(sdo),\n" v += "\t.SCLK(sclk),\n" if pll_type == "SB_PLL40_2F_PAD" or pll_type == "SB_PLL40_2_PAD": v += "\t.PLLOUTCOREA(plloutcorea),\n" v += "\t.PLLOUTGLOBALA(plloutglobala),\n" v += "\t.PLLOUTCOREB(plloutcoreb),\n" v += "\t.PLLOUTGLOBALB(plloutglobalb),\n" else: v += "\t.PLLOUTCORE(plloutcorea),\n" v += "\t.PLLOUTGLOBAL(plloutglobala),\n" v += "\t.DYNAMICDELAY({dynamicdelay_7, dynamicdelay_6, dynamicdelay_5, dynamicdelay_4, dynamicdelay_3, dynamicdelay_2, dynamicdelay_1, dynamicdelay_0})\n" v += ");\n" v += "defparam pll_inst.DIVR = " + get_param_value("DIVR", 4, fuzz_bit) + ";\n" v += "defparam pll_inst.DIVF = " + get_param_value("DIVF", 7, fuzz_bit) + ";\n" v += "defparam pll_inst.DIVQ = " + get_param_value("DIVQ", 3, fuzz_bit) + ";\n" v += "defparam pll_inst.FILTER_RANGE = " + get_param_value("FILTER_RANGE", 3, fuzz_bit) + ";\n" if fuzz_bit == "FEEDBACK_PATH_0": v += "defparam pll_inst.FEEDBACK_PATH = \"SIMPLE\";\n" elif fuzz_bit == "FEEDBACK_PATH_1": v += "defparam pll_inst.FEEDBACK_PATH = \"PHASE_AND_DELAY\";\n" elif fuzz_bit == "FEEDBACK_PATH_2": v += "defparam pll_inst.FEEDBACK_PATH = \"EXTERNAL\";\n" else: v += "defparam pll_inst.FEEDBACK_PATH = \"DELAY\";\n" v += "defparam pll_inst.DELAY_ADJUSTMENT_MODE_FEEDBACK = \"" + ("DYNAMIC" if (fuzz_bit == "DELAY_ADJMODE_FB") else "FIXED") + "\";\n" v += "defparam pll_inst.FDA_FEEDBACK = " + get_param_value("FDA_FEEDBACK", 4, fuzz_bit) + ";\n" v += "defparam pll_inst.DELAY_ADJUSTMENT_MODE_RELATIVE = \"" + ("DYNAMIC" if (fuzz_bit == "DELAY_ADJMODE_REL") else "FIXED") + "\";\n" v += "defparam pll_inst.FDA_RELATIVE = " + get_param_value("FDA_RELATIVE", 4, fuzz_bit) + ";\n" v += "defparam pll_inst.SHIFTREG_DIV_MODE = " + ("1'b1" if (fuzz_bit == "SHIFTREG_DIV_MODE") else "1'b0") + ";\n" if pll_type == "SB_PLL40_2F_PAD" or pll_type == "SB_PLL40_2_PAD": if pll_type == "SB_PLL40_2F_PAD": if fuzz_bit == "PLLOUT_SELECT_A_0": v += "defparam pll_inst.PLLOUT_SELECT_PORTA = \"GENCLK_HALF\";\n" elif fuzz_bit == "PLLOUT_SELECT_A_1": v += "defparam pll_inst.PLLOUT_SELECT_PORTA = \"SHIFTREG_90deg\";\n" else: v += "defparam pll_inst.PLLOUT_SELECT_PORTA = \"GENCLK\";\n" if fuzz_bit == "PLLOUT_SELECT_B_0": v += "defparam pll_inst.PLLOUT_SELECT_PORTB = \"GENCLK_HALF\";\n" elif fuzz_bit == "PLLOUT_SELECT_B_1": v += "defparam pll_inst.PLLOUT_SELECT_PORTB = \"SHIFTREG_90deg\";\n" else: v += "defparam pll_inst.PLLOUT_SELECT_PORTB = \"GENCLK\";\n" else: if fuzz_bit == "PLLOUT_SELECT_A_0": v += "defparam pll_inst.PLLOUT_SELECT = \"GENCLK_HALF\";\n" elif fuzz_bit == "PLLOUT_SELECT_A_1": v += "defparam pll_inst.PLLOUT_SELECT = \"SHIFTREG_90deg\";\n" else: v += "defparam pll_inst.PLLOUT_SELECT = \"GENCLK\";\n" v += "defparam pll_inst.TEST_MODE = " + ("1'b1" if (fuzz_bit == "TEST_MODE") else "1'b0") + ";\n" return v; def make_vlog(fuzz_bit): vlog = code_prefix vlog += inst_pll(fuzz_bit) vlog += "endmodule" return vlog known_bits = [] # Set to true to continue even if multiple bits are changed (needed because # of the issue discusssed below) show_all_bits = False #TODO: make this an argument device = "up5k" #TODO: environment variable? #HACK: icecube doesn't let you set all of the DIVQ bits to 0, #which makes fuzzing early on annoying as there is never a case #with just 1 bit set. So a tiny bit of semi-manual work is needed #first to discover this (basically run this script with show_all_bits=True #and look for the stuck bit) #TODO: clever code could get rid of this divq_bit0 = { "up5k" : (11, 31, 3) } #Return a list of PLL config bits in the format (x, y, bit) def parse_exp(expfile): current_x = 0 current_y = 0 bits = [] with open(expfile, 'r') as f: for line in f: splitline = line.split(' ') if splitline[0] == ".io_tile": current_x = int(splitline[1]) current_y = int(splitline[2]) elif splitline[0] == "PLL": if splitline[1][:10] == "PLLCONFIG_": bitidx = int(splitline[1][10:]) bits.append((current_x, current_y, bitidx)) return bits #Convert a bit tuple as returned from the above to a nice string def bit_to_str(bit): return "(%d, %d, \"PLLCONFIG_%d\")" % bit #The main fuzzing function def do_fuzz(): if not os.path.exists("./work_pllauto"): os.mkdir("./work_pllauto") known_bits.append(divq_bit0[device]) with open("pll_data_" + device + ".txt", 'w') as dat: for fuzz_bit in fuzz_bits: vlog = make_vlog(fuzz_bit) with open("./work_pllauto/pllauto.v", 'w') as f: f.write(vlog) retval = os.system("bash ../../icecube.sh -" + device + " ./work_pllauto/pllauto.v > ./work_pllauto/icecube.log 2>&1") if retval != 0: sys.stderr.write('ERROR: icecube returned non-zero error code\n') sys.exit(1) retval = os.system("../../../icebox/icebox_explain.py ./work_pllauto/pllauto.asc > ./work_pllauto/pllauto.exp") if retval != 0: sys.stderr.write('ERROR: icebox_explain returned non-zero error code\n') sys.exit(1) pll_bits = parse_exp("./work_pllauto/pllauto.exp") new_bits = [] for set_bit in pll_bits: if not (set_bit in known_bits): new_bits.append(set_bit) if len(new_bits) == 0: sys.stderr.write('ERROR: no new bits set when setting config bit ' + fuzz_bit + '\n') sys.exit(1) if len(new_bits) > 1: sys.stderr.write('ERROR: multiple new bits set when setting config bit ' + fuzz_bit + '\n') for bit in new_bits: sys.stderr.write('\t' + bit_to_str(bit) + '\n') if not show_all_bits: sys.exit(1) if len(new_bits) == 1: known_bits.append(new_bits[0]) #print DIVQ_0 at the right moment, as it's not fuzzed normally if fuzz_bit == "DIVQ_1": print(("\"DIVQ_0\":").ljust(24) + bit_to_str(divq_bit0[device]) + ",") dat.write(("\"DIVQ_0\":").ljust(24) + bit_to_str(divq_bit0[device]) + ",\n") print(("\"" + fuzz_bit + "\":").ljust(24) + bit_to_str(new_bits[0]) + ",") dat.write(("\"" + fuzz_bit + "\":").ljust(24) + bit_to_str(new_bits[0]) + ",\n") do_fuzz()