| #!/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), |
| "lm4k" : (11, 0, 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() |