1#!/usr/bin/env python3
2
3import os, sys
4# PLL automatic fuzzing script (WIP)
5
6device = "up5k"
7
8# PLL config bits to be fuzzed
9# These must be in an order such that a config with bit i set doesn't set any other undiscovered bits yet
10# e.g. PLL_TYPE must be fuzzed first as these will need to be set later on by virtue of enabling the PLL
11
12fuzz_bits = [
13    "PLLTYPE_1",
14    "PLLTYPE_2",
15    "PLLTYPE_0", #NB: as per the rule above this comes later is it can only be set by also setting 1 or 2
16
17    "FEEDBACK_PATH_0",
18    "FEEDBACK_PATH_1",
19    "FEEDBACK_PATH_2",
20
21    "PLLOUT_SELECT_A_0",
22    "PLLOUT_SELECT_A_1",
23
24    "PLLOUT_SELECT_B_0",
25    "PLLOUT_SELECT_B_1",
26
27    "SHIFTREG_DIV_MODE",
28
29    "FDA_FEEDBACK_0",
30    "FDA_FEEDBACK_1",
31    "FDA_FEEDBACK_2",
32    "FDA_FEEDBACK_3",
33
34    "FDA_RELATIVE_0",
35    "FDA_RELATIVE_1",
36    "FDA_RELATIVE_2",
37    "FDA_RELATIVE_3",
38
39    "DIVR_0",
40    "DIVR_1",
41    "DIVR_2",
42    "DIVR_3",
43
44    "DIVF_0",
45    "DIVF_1",
46    "DIVF_2",
47    "DIVF_3",
48    "DIVF_4",
49    "DIVF_5",
50    "DIVF_6",
51
52    #DIVQ_0 is missing, see comments later on
53    "DIVQ_1",
54    "DIVQ_2",
55
56    "FILTER_RANGE_0",
57    "FILTER_RANGE_1",
58    "FILTER_RANGE_2",
59
60    "TEST_MODE",
61
62    "DELAY_ADJMODE_FB", #these come at the end in case they set FDA_RELATIVE??
63    "DELAY_ADJMODE_REL"
64]
65
66# Boilerplate code based on the icefuzz script
67code_prefix = """
68module 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);
69input packagepin;
70input a;
71input b;
72output w;
73output x;
74output reg y;
75output reg z;
76input extfeedback;
77input bypass;
78input resetb;
79output lock;
80input latchinputvalue;
81input sdi;
82output sdo;
83input sclk;
84wire plloutcorea;
85wire plloutcoreb;
86wire plloutglobala;
87wire plloutglobalb;
88assign w = plloutcorea ^ a;
89assign x = plloutcoreb ^ b;
90always @(posedge plloutglobala) y <= a;
91always @(posedge plloutglobalb) z <= b;
92input dynamicdelay_0;
93input dynamicdelay_1;
94input dynamicdelay_2;
95input dynamicdelay_3;
96input dynamicdelay_4;
97input dynamicdelay_5;
98input dynamicdelay_6;
99input dynamicdelay_7;
100"""
101
102def get_param_value(param_name, param_size, fuzz_bit):
103    param = str(param_size) + "'b";
104    for i in range(param_size - 1, -1, -1):
105        if fuzz_bit == param_name + "_" + str(i):
106            param += '1'
107        else:
108            param += '0'
109    return param
110def inst_pll(fuzz_bit):
111    pll_type = "SB_PLL40_2F_PAD" #default to this as it's most flexible
112
113    if fuzz_bit == "PLLTYPE_0":
114        pll_type = "SB_PLL40_CORE"
115    elif fuzz_bit == "PLLTYPE_1":
116        pll_type = "SB_PLL40_PAD"
117    elif fuzz_bit == "PLLTYPE_2":
118        pll_type = "SB_PLL40_2_PAD"
119
120    v = pll_type + " pll_inst (\n"
121    if pll_type == "SB_PLL40_CORE":
122        v += "\t.REFERENCECLK(referenceclk), \n"
123    else:
124        v += "\t.PACKAGEPIN(packagepin), \n"
125    v += "\t.RESETB(resetb),\n"
126    v += "\t.BYPASS(bypass),\n"
127    v += "\t.EXTFEEDBACK(extfeedback),\n"
128    v += "\t.LOCK(lock),\n"
129    v += "\t.LATCHINPUTVALUE(latchinputvalue),\n"
130    v += "\t.SDI(sdi),\n"
131    v += "\t.SDO(sdo),\n"
132    v += "\t.SCLK(sclk),\n"
133    if pll_type == "SB_PLL40_2F_PAD" or pll_type == "SB_PLL40_2_PAD":
134      v += "\t.PLLOUTCOREA(plloutcorea),\n"
135      v += "\t.PLLOUTGLOBALA(plloutglobala),\n"
136      v += "\t.PLLOUTCOREB(plloutcoreb),\n"
137      v += "\t.PLLOUTGLOBALB(plloutglobalb),\n"
138    else:
139      v += "\t.PLLOUTCORE(plloutcorea),\n"
140      v += "\t.PLLOUTGLOBAL(plloutglobala),\n"
141    v += "\t.DYNAMICDELAY({dynamicdelay_7, dynamicdelay_6, dynamicdelay_5, dynamicdelay_4, dynamicdelay_3, dynamicdelay_2, dynamicdelay_1, dynamicdelay_0})\n"
142    v += ");\n"
143
144    v += "defparam pll_inst.DIVR = " + get_param_value("DIVR", 4, fuzz_bit) + ";\n"
145    v += "defparam pll_inst.DIVF = " + get_param_value("DIVF", 7, fuzz_bit) + ";\n"
146    v += "defparam pll_inst.DIVQ = " + get_param_value("DIVQ", 3, fuzz_bit) + ";\n"
147    v += "defparam pll_inst.FILTER_RANGE = " + get_param_value("FILTER_RANGE", 3, fuzz_bit) + ";\n"
148
149    if fuzz_bit == "FEEDBACK_PATH_0":
150        v += "defparam pll_inst.FEEDBACK_PATH = \"SIMPLE\";\n"
151    elif fuzz_bit == "FEEDBACK_PATH_1":
152        v += "defparam pll_inst.FEEDBACK_PATH = \"PHASE_AND_DELAY\";\n"
153    elif fuzz_bit == "FEEDBACK_PATH_2":
154        v += "defparam pll_inst.FEEDBACK_PATH = \"EXTERNAL\";\n"
155    else:
156        v += "defparam pll_inst.FEEDBACK_PATH = \"DELAY\";\n"
157
158    v += "defparam pll_inst.DELAY_ADJUSTMENT_MODE_FEEDBACK = \"" + ("DYNAMIC" if (fuzz_bit == "DELAY_ADJMODE_FB") else "FIXED") + "\";\n"
159    v += "defparam pll_inst.FDA_FEEDBACK = " + get_param_value("FDA_FEEDBACK", 4, fuzz_bit) + ";\n"
160    v += "defparam pll_inst.DELAY_ADJUSTMENT_MODE_RELATIVE = \"" + ("DYNAMIC" if (fuzz_bit == "DELAY_ADJMODE_REL") else "FIXED") + "\";\n"
161    v += "defparam pll_inst.FDA_RELATIVE = " + get_param_value("FDA_RELATIVE", 4, fuzz_bit) + ";\n"
162    v += "defparam pll_inst.SHIFTREG_DIV_MODE = " + ("1'b1" if (fuzz_bit == "SHIFTREG_DIV_MODE") else "1'b0") + ";\n"
163
164
165
166    if pll_type == "SB_PLL40_2F_PAD" or pll_type == "SB_PLL40_2_PAD":
167        if pll_type == "SB_PLL40_2F_PAD":
168            if fuzz_bit == "PLLOUT_SELECT_A_0":
169                v += "defparam pll_inst.PLLOUT_SELECT_PORTA = \"GENCLK_HALF\";\n"
170            elif fuzz_bit == "PLLOUT_SELECT_A_1":
171                v += "defparam pll_inst.PLLOUT_SELECT_PORTA = \"SHIFTREG_90deg\";\n"
172            else:
173                v += "defparam pll_inst.PLLOUT_SELECT_PORTA = \"GENCLK\";\n"
174        if fuzz_bit == "PLLOUT_SELECT_B_0":
175            v += "defparam pll_inst.PLLOUT_SELECT_PORTB = \"GENCLK_HALF\";\n"
176        elif fuzz_bit == "PLLOUT_SELECT_B_1":
177            v += "defparam pll_inst.PLLOUT_SELECT_PORTB = \"SHIFTREG_90deg\";\n"
178        else:
179            v += "defparam pll_inst.PLLOUT_SELECT_PORTB = \"GENCLK\";\n"
180    else:
181        if fuzz_bit == "PLLOUT_SELECT_A_0":
182            v += "defparam pll_inst.PLLOUT_SELECT = \"GENCLK_HALF\";\n"
183        elif fuzz_bit == "PLLOUT_SELECT_A_1":
184            v += "defparam pll_inst.PLLOUT_SELECT = \"SHIFTREG_90deg\";\n"
185        else:
186            v += "defparam pll_inst.PLLOUT_SELECT = \"GENCLK\";\n"
187    v += "defparam pll_inst.TEST_MODE = " + ("1'b1" if (fuzz_bit == "TEST_MODE") else "1'b0") + ";\n"
188
189    return v;
190
191def make_vlog(fuzz_bit):
192    vlog = code_prefix
193    vlog += inst_pll(fuzz_bit)
194    vlog += "endmodule"
195    return vlog
196
197known_bits = []
198
199# Set to true to continue even if multiple bits are changed (needed because
200# of the issue discusssed below)
201show_all_bits = False #TODO: make this an argument
202
203device = "up5k" #TODO: environment variable?
204
205#HACK: icecube doesn't let you set all of the DIVQ bits to 0,
206#which makes fuzzing early on annoying as there is never a case
207#with just 1 bit set. So a tiny bit of semi-manual work is needed
208#first to discover this (basically run this script with show_all_bits=True
209#and look for the stuck bit)
210#TODO: clever code could get rid of this
211divq_bit0 = {
212    "up5k" : (11, 31, 3),
213    "lm4k" : (11, 0, 3)
214}
215
216#Return a list of PLL config bits in the format (x, y, bit)
217def parse_exp(expfile):
218    current_x = 0
219    current_y = 0
220    bits = []
221    with open(expfile, 'r') as f:
222        for line in f:
223            splitline = line.split(' ')
224            if splitline[0] == ".io_tile":
225                current_x = int(splitline[1])
226                current_y = int(splitline[2])
227            elif splitline[0] == "PLL":
228                if splitline[1][:10] == "PLLCONFIG_":
229                    bitidx = int(splitline[1][10:])
230                    bits.append((current_x, current_y, bitidx))
231    return bits
232
233#Convert a bit tuple as returned from the above to a nice string
234def bit_to_str(bit):
235    return "(%d, %d, \"PLLCONFIG_%d\")" % bit
236
237#The main fuzzing function
238def do_fuzz():
239    if not os.path.exists("./work_pllauto"):
240        os.mkdir("./work_pllauto")
241    known_bits.append(divq_bit0[device])
242    with open("pll_data_" + device + ".txt", 'w') as dat:
243        for fuzz_bit in fuzz_bits:
244            vlog = make_vlog(fuzz_bit)
245            with open("./work_pllauto/pllauto.v", 'w') as f:
246                f.write(vlog)
247            retval = os.system("bash ../../icecube.sh -" + device + " ./work_pllauto/pllauto.v > ./work_pllauto/icecube.log 2>&1")
248            if retval != 0:
249                sys.stderr.write('ERROR: icecube returned non-zero error code\n')
250                sys.exit(1)
251            retval = os.system("../../../icebox/icebox_explain.py ./work_pllauto/pllauto.asc > ./work_pllauto/pllauto.exp")
252            if retval != 0:
253                sys.stderr.write('ERROR: icebox_explain returned non-zero error code\n')
254                sys.exit(1)
255            pll_bits = parse_exp("./work_pllauto/pllauto.exp")
256            new_bits = []
257            for set_bit in pll_bits:
258                if not (set_bit in known_bits):
259                    new_bits.append(set_bit)
260            if len(new_bits) == 0:
261                sys.stderr.write('ERROR: no new bits set when setting config bit ' + fuzz_bit + '\n')
262                sys.exit(1)
263            if len(new_bits) > 1:
264                sys.stderr.write('ERROR: multiple new bits set when setting config bit ' + fuzz_bit + '\n')
265                for bit in new_bits:
266                    sys.stderr.write('\t' + bit_to_str(bit) + '\n')
267                if not show_all_bits:
268                    sys.exit(1)
269            if len(new_bits) == 1:
270                known_bits.append(new_bits[0])
271                #print DIVQ_0 at the right moment, as it's not fuzzed normally
272                if fuzz_bit == "DIVQ_1":
273                    print(("\"DIVQ_0\":").ljust(24) + bit_to_str(divq_bit0[device]) + ",")
274                    dat.write(("\"DIVQ_0\":").ljust(24) + bit_to_str(divq_bit0[device]) + ",\n")
275                print(("\"" + fuzz_bit + "\":").ljust(24) + bit_to_str(new_bits[0]) + ",")
276                dat.write(("\"" + fuzz_bit + "\":").ljust(24) + bit_to_str(new_bits[0]) + ",\n")
277do_fuzz()