def run(code): return toolchain.run( f"module top(input C1, R, output O); " f"wire Q; TRI tri(Q, 1'b0, O); " f"{code} " f"endmodule", { 'C1': pinout['C1'], 'R': pinout['R'], 'ff': str(601 + macrocell_idx), }, f"{device_name}-{package}") f_dff = run("DFF ff(.CLK(C1), .D(1'b0), .Q(Q));") f_dffar = run("DFFAR ff(.CLK(C1), .AR(R), .D(1'b0), .Q(Q));") # According to the diagram, there is a 3:1 mux driving AR, allowing to choose between # GCLR/(GCLR|PT3)/PT3. This is likely implemented as (GCLR&GCLRen)|(PT3&PT3en), where # the latter is decided by pt3_mux. The fitter seems to reject all attempts to OR # dedicated GCLR routing with a product term, however it does work on hardware the way # it is described in datasheet. "No reset" is a choice supported by the fitter and # the datasheet text but the diagram implies it's impossible (without losing PT3). # https://www.dataman.com/media/datasheet/Atmel/ATF15xxAE_doc2398.pdf macrocell.update({ 'gclr_mux': bitdiff.describe(1, { 'GND': f_dff, 'GCLR': f_dffar }) })
assert False seen_pt1s.update(new_pt1s) new_pt1, = new_pt1s # Now we know which macrocell to attribute the S9 flip to. # # One might wonder, what happens to the foldback net when PT1 is a part of the sum # ter? Based on hardware testing, pt1_mux routes PT1 to either foldback or sum # term, whichever is selected, and routes 0 to the other net. (This happens before # the inverter.) Note that foldback has a special case chosen by particular values # of xor_a_mux, xor_b_mux, and xor_invert. device['macrocells'][new_pt1].update({ 'pt1_mux': bitdiff.describe( 1, { 'flb': f_curr, 'sum': f_prev }, scope=range(*device['ranges']['macrocells'])) }) # The macrocell that is driven by the foldback now had PT4 changed, find it. # But take into account that the fitter may have rearranged the foldback pterms # across macrocells. new_pt4s = set() for macrocell_name in block['macrocells']: pt4_fuse_range = \ range(*device['macrocells'][macrocell_name]['pterm_ranges']['PT4']) f_prev_pt4 = f_prev[pt4_fuse_range.start:pt4_fuse_range. stop] f_curr_pt4 = f_curr[pt4_fuse_range.start:pt4_fuse_range. stop]
f"endmodule", { 'CLK1': pinout['C1'], 'CLK2': pinout['C2'], 'O1': pinout[other1_macrocell['pad']], 'O2': pinout[other2_macrocell['pad']], 'dff1': str(601 + macrocell_idx), # Pretty gross to rely on autogenerated names, but the other # netlist/constraint sets I came up were even less reliable. 'Com_Ctrl_13': str(601 + macrocell_idx), }, f"{device_name}-{package}", **kwargs) f_sync = run(f"wire Y1; XOR2 x1(CLK1, CLK2, Y1); " f"wire Q1; DFF dff1(1'b0, Y1, Q1); " f"TRI t1(1'b0, Q1, O1); " f"TRI t2(1'b0, Q1, O2); ") f_comb = run(f"wire Y1; XOR2 x1(CLK1, CLK2, Y1); " f"TRI t1(1'b0, Y1, O1); " f"TRI t2(1'b0, Y1, O2); ") # Feedback can be taken from either XOR term or FF/latch output. macrocell.update({ 'fb_mux': bitdiff.describe(1, { 'comb': f_comb, 'sync': f_sync }), })
'GCLK2': pinout['C2'], 'GCLK3': pinout[gclk3_pad], **{ f"GOE{1+n}": pinout[pad[:-4]] for n, pad in enumerate(goe_pads) }, 'Q': pinout[device['macrocells']['MC1']['pad']], }, f"{device_name}-{package}", **kwargs) f_gclr_pos = run(f"DFFAR ff(.CLK(1'b0), .AR(GCLR), .D(1'b0), .Q(Q));") f_gclr_neg = run(f"wire GCLRn; INV in(GCLR, GCLRn); " f"DFFAR ff(.CLK(1'b0), .AR(GCLRn), .D(1'b0), .Q(Q));") gclr_switch.update({ 'invert': bitdiff.describe(1, { 'off': f_gclr_pos, 'on': f_gclr_neg, }), }) for gclk_name, gclk_switch in gclk_switches.items(): f_gclk_pos = run(f"DFF ff(.CLK({gclk_name}), .D(1'b0), .Q(Q));") f_gclk_neg = run(f"wire {gclk_name}n; INV in({gclk_name}, {gclk_name}n); " f"DFF ff(.CLK({gclk_name}n), .D(1'b0), .Q(Q));") macrocell = device['macrocells']['MC1'] gclk_mux_option = macrocell['gclk_mux'] gclk_mux_value = 0 for n_fuse, fuse in enumerate(gclk_mux_option['fuses']): gclk_mux_value += f_gclk_pos[fuse] << n_fuse for gclk_mux_net, gclk_mux_net_value in gclk_mux_option['values'].items(): if gclk_mux_value == gclk_mux_net_value:
device['macrocells'].items()): progress(1) def run(code): return toolchain.run( f"module top(input OE, CLK1, CLK2, CLK3, output O);" f"wire Q; TRI tri(Q, 1'b0, O); " f"{code} " f"endmodule", { 'CLK1': pinout['C1'], 'CLK2': pinout['C2'], 'CLK3': pinout[gclk3_pad], 'ff': str(601 + macrocell_idx), }, f"{device_name}-{package}") f_clk1 = run("DFF ff(.CLK(CLK1), .D(CLK3), .Q(Q));") f_clk2 = run("DFF ff(.CLK(CLK2), .D(CLK3), .Q(Q));" ) # also happens to be 00 f_clk3 = run("DFF ff(.CLK(CLK3), .D(CLK3), .Q(Q));") # 1 unused fuse combination # http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-3614-CPLD-ATF15-Overview.pdf macrocell.update({ 'gclk_mux': bitdiff.describe(2, { 'GCLK2': f_clk2, 'GCLK3': f_clk3, 'GCLK1': f_clk1 }) })
nodes = {} for (goe_name, goe_mux), goe_pad in zip(goe_muxes.items(), goe_pads): f_goe = run(f"TRI t(Y, {goe_pad}, O);", strategy={"Global_OE": goe_pad}) for offset, goe_mux_fuse in enumerate(goe_mux['fuses']): # We know what the GOE mux choice is. assert f_goe[goe_mux_fuse] == ( goe_mux['values'][goe_pad] >> offset) & 1 f_goe[goe_mux_fuse] = 1 # don't care nodes[goe_name] = f_goe f_gnd = run(f"wire BY; BIBUF b(Y, 1'b0, BY, O);") nodes['GND'] = f_gnd # The VCC choice of OE mux is shared with PT5 choice; if pt5_func is as, or # pt5_func is oe but pt5_mux is sum, then it is VCC, otherwise it is PT5. # Choosing pure VCC is a bit annoying (it switches pt5_func and/or pt5_mux depending # on how you do it), so choose PT5 and mask out the PT5 fuses instead. f_vcc = run(f"wire BY; BIBUF b(Y, CLK1, BY, O);") for pt5_fuse in range(*device['macrocells'][macrocell_name] ['pterm_ranges']['PT5']): f_vcc[pt5_fuse] = 0 # don't care nodes['VCC_pt5'] = f_vcc macrocell.update({ 'oe_mux': bitdiff.describe(3, nodes), })
progress(1) def run(code): return toolchain.run( f"module top(input CLK1); " f"wire Q, X, Y; OR2 o1(Q, X, Y); BUF b1(Y, X);" f"{code} " f"endmodule", { 'CLK1': pinout['C1'], 'ff': str(601 + macrocell_idx), }, f"{device_name}-{package}") f_off = run("DFF ff(1'b0, 1'b0, Q);") f_on = run("DFF ff(1'b0, CLK1, Q);") # Datasheet describes two kinds of power management options: "reduced power" feature # (controllable in fitter using the MC_power strategy) and "power down" feature. For # the latter it mentions that "Unused product terms are automatically disabled by # the compiler to decrease power consumption." # # When powered down, the PTs output 0 only if all of their fuses are programmed as 0. # Otherwise the PTs with non-zero fuses will output fixed 1. It is not clear why. macrocell.update({ 'pt_power': bitdiff.describe(1, { 'off': f_off, 'on': f_on }, scope=range(*device['ranges']['macrocells'])) })
package, pinout = next(iter(device['pins'].items())) for macrocell_idx, (macrocell_name, macrocell) in enumerate(device['macrocells'].items()): progress(1) def run(code): return toolchain.run( f"module top(input CLK2, OE1, output O); " f"wire Q; TRI tri(Q, 1'b0, O); " f"{code} " f"endmodule", { 'CLK2': pinout['C2'], 'OE1': pinout['E1'], 'ff': str(601 + macrocell_idx), }, f"{device_name}-{package}") # Use CLK2 because it has the same bit pattern as the global clock mux default. f_pt4_clk = run("DFFE ff(.CLK(OE1), .CE(1'b1), .D(1'b0), .Q(Q));") f_pt4_ce = run("DFFE ff(.CLK(CLK2), .CE(OE1), .D(1'b0), .Q(Q));") # If PT4 is not used in the sum term it can be routed to exactly one of clock or # clock enable. If it is routed to clock, clock enable is fixed at 1. If it is routed # to clock enable, clock is selected according to the global clock mux. # http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-3614-CPLD-ATF15-Overview.pdf macrocell.update({ 'pt4_func': bitdiff.describe(1, {'clk': f_pt4_clk, 'ce': f_pt4_ce}), })
f"module top(input C1, C2, E1, R, output O); " f"wire Q; TRI tri(Q, 1'b0, O); " f"wire Y1, Y2, Y3; " f"AND2 a1(C1, C2, Y1); " f"AND2 a2(C2, E1, Y2); " f"AND2 a3(E1, R, Y3); " f"{code} " f"endmodule", { 'C1': pinout['C1'], 'C2': pinout['C2'], 'E1': pinout['E1'], 'R': pinout['R'], 'ff': str(601 + macrocell_idx), }, f"{device_name}-{package}", strategy={'xor_synthesis': 'on'}) f_sum = run( "wire Y4; OR3 o1(Y2, Y1, Y3, Y4); DFF ff(1'b0, Y4, Q);") f_ar = run( "wire Y4; OR2 o1(Y1, Y2, Y4); DFFAR ff(1'b0, Y3, Y4, Q);") # PT3 can be either a part of the sum term, or serve as async reset. macrocell.update({ 'pt3_mux': bitdiff.describe(1, { 'ar': f_ar, 'sum': f_sum }), })
'Q': pinout[macrocell['pad']], }, f"{device_name}-{package}") f_n = run("OR3 o1 (CLK1, CLK2, OE1, Q);") f_p = run("AND3I3 ai1(CLK1, CLK2, OE1, Q);") # According to the datasheet, "At [power on reset], all registers will be initialized, # and the state of each output will depend on the polarity of its buffer." Indeed, # the XOR term inversion fuse controls the power-up state of the flip-flop as well. # Interestingly, it does not affect AR or AS inputs (AR always resets FF to 0, # AS to 1), does not affect either of the fast FF input paths, and affects output # of buried FFs. # # It seems more accurate to say this bit controls reset value and combinatorial term # polarity rather than output buffer polarity; it appears to be an inverter placed # between the XOR gate and the 3:1 FF D mux on the diagram. # https://www.dataman.com/media/datasheet/Atmel/ATF15xxAE_doc2398.pdf macrocell.update({ 'xor_invert': bitdiff.describe(1, { 'off': f_n, 'on': f_p }), 'reset': bitdiff.describe(1, { 'GND': f_p, 'VCC': f_n }), })
f"wire Q; TRI tri(Q, 1'b0, O); " f"{code} " f"endmodule", { 'CLK': pinout['C1'], 'ff': str(601 + macrocell_idx), }, f"{device_name}-{package}", **kwargs) f_dff = run("DFF ff(.CLK(CLK), .D(1'b0), .Q(Q));") f_latch = run("LATCH ff(.EN(CLK), .D(1'b0), .Q(Q));") if has_tff: f_tff = run("TFF ff(.CLK(CLK), .T(1'b0), .Q(Q));", strategy={'no_tff': 'off'}) if has_tff: macrocell.update({ 'storage': bitdiff.describe(2, { 'dff': f_dff, 'tff': f_tff, 'latch': f_latch }) }) else: macrocell.update({ 'storage': bitdiff.describe(1, { 'dff': f_dff, 'latch': f_latch }) })
def run(code): return toolchain.run( f"module top(input CLK, output O); " f"wire Q; TRI tri(Q, 1'b0, O); " f"{code} " f"endmodule", { 'CLK': pinout['C1'], 'ff': str(601 + macrocell_idx), }, f"{device_name}-{package}") f_dff0 = run("DFF ff(.CLK(CLK), .D(1'b0), .Q(Q));") f_dff1 = run("DFF ff(.CLK(CLK), .D(1'b1), .Q(Q));") # The VCC choice of XOR A mux is shared with PT2 choice: if pt2_mux is sum, then it is # VCC, otherwise it is PT2. Further, the XOR A mux is linked to CASOUT: if xor_a_mux # is sum, then CASOUT is 0, otherwise CASOUT is ST. macrocell.update({ 'xor_a_mux': bitdiff.describe(1, { 'sum': f_dff0, 'VCC_pt2': f_dff1 }), 'cas_mux': bitdiff.describe(1, { 'GND': f_dff0, 'sum': f_dff1 }) })
f"module top(input C1, C2, C3, A, output Q); " f"{code} " f"endmodule", { 'C1': pinout['C1'], 'C2': pinout['C2'], 'C3': pinout[gclk3_pad], }, f"{device_name}-{package}", **kwargs) if device_name.endswith("AS"): f_pin_keep_off = run(strategy={'pin_keep':'off'}) f_pin_keep_on = run(strategy={'pin_keep':'on'}) config.update({ 'termination': bitdiff.describe(1, { 'high_z': f_pin_keep_off, 'bus_keeper': f_pin_keep_on, }), }) for index, pin in enumerate(('pd1', 'pd2')): f_pwrdn_n_off = run(strategy={pin:'off'}) f_pwrdn_n_on = run(strategy={pin:'on'}) set_mc_input(f_pwrdn_n_on, f"MC{pwrdn_pads[index][1:]}") config.update({ f"{pin}_pin_func": bitdiff.describe(1, { 'user': f_pwrdn_n_off, 'pd': f_pwrdn_n_on, }), }) if device_name.endswith("AS"):
progress(device_name) package, pinout = next(iter(device['pins'].items())) for macrocell_name, macrocell in device['macrocells'].items(): progress(1) if macrocell['pad'] not in pinout: print( f"Skipping {macrocell_name} on {device_name} because it is not bonded out" ) continue def run(code): return toolchain.run( f"module top(input C2, R, output Q); {code} endmodule", { 'C2': pinout['C2'], 'R': pinout['R'], 'Q': pinout[macrocell['pad']], }, f"{device_name}-{package}") f_as = run(f"DFFAS x(.CLK(C2), .AS(R), .D(R), .Q(Q));") f_oe = run(f"wire X; DFF x(.CLK(C2), .D(R), .Q(X)); " f"BUFTH t(.A(X), .ENA(R), .Q(Q));") # http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-3614-CPLD-ATF15-Overview.pdf macrocell.update( {'pt5_func': bitdiff.describe(1, { 'as': f_as, 'oe': f_oe })})
for device_name, device in db.items(): progress(device_name) package, pinout = next(iter(device['pins'].items())) for macrocell_idx, (macrocell_name, macrocell) in enumerate( device['macrocells'].items()): progress(1) def run(code): return toolchain.run( f"module top(input CLK2, OE1, output O); " f"wire Q; TRI tri(Q, 1'b0, O); " f"{code} " f"endmodule", { 'CLK2': pinout['C2'], 'OE1': pinout['E1'], 'ff': str(601 + macrocell_idx), }, f"{device_name}-{package}") f_mux_1 = run("DFF ff(.CLK(1'b1), .D(1'b0), .Q(Q));") f_mux_pt4 = run("DFF ff(.CLK(1'b0), .D(1'b0), .Q(Q));") # PT4 can be either a part of the sum term, or serve as clock/clock enable. macrocell.update({ 'pt4_mux': bitdiff.describe(1, { 'clk_ce': f_mux_pt4, 'sum': f_mux_1 }), })
if device_name.endswith('BE'): f_hyst = run_o(schmitt_trigger='O') f_pu = run_o(pull_up='O') f_pk = run_o(pin_keep='O') # overrides pull-up if has_sstl: f_ttl = run_i(voltage_level_A='3.3', voltage_level_B='3.3', SSTL_input='I2') f_sstl = run_i(voltage_level_A='3.3', voltage_level_B='3.3', SSTL_input='I1,I2') macrocell.update({ 'slew_rate': bitdiff.describe(1, { 'fast': f_fast, 'slow': f_out }), 'output_driver': bitdiff.describe(1, { 'push_pull': f_out, 'open_drain': f_oc }), }) if device_name.endswith('AS'): macrocell.update({ 'low_power': bitdiff.describe(1, { 'off': f_out, 'on': f_lp }), })
'C2': pinout['C2'], 'E1': pinout['E1'], 'R': pinout['R'], 'I': pinout[other_macrocell['pad']], 'Q': pinout[macrocell['pad']], }, f"{device_name}-{package}", strategy={'xor_synthesis': 'on'}) # Took me a long time to find a netlist this pathological. f_sum = run(f"wire Y1, Y2, Y3, Y4, Y5; " f"AND2 a1(C1, C2, Y1); " f"AND2 a2(I, R, Y2); " f"OR2 o1(Y2, E1, Y3); " f"XOR2 x1(Y1, Y3, Y5); " f"DFFAR d(Y1, Y1, Y5, Q); ") f_as = run(f"wire Y1, Y2, Y3, Y4, Y5; " f"AND2 a1(C1, C2, Y1); " f"AND2 a2(I, R, Y2); " f"XOR2 x1(Y1, Y2, Y5); " f"DFFARS d(Y1, Y1, E1, Y5, Q); ") # PT5 can be either a part of the sum term, or serve as async set/output enable. macrocell.update({ 'pt5_mux': bitdiff.describe(1, { 'as_oe': f_as, 'sum': f_sum }), })
f"input IO; DFFAS ff(CLK1, OE1, IO, Q);") # 00 # There is no way to enable OE with fast inlatch also enabled, so do this ourselves. oe_mux_fuses = macrocell['oe_mux']['fuses'] oe_mux_gnd = macrocell['oe_mux']['values']['GND'] oe_mux_vcc = macrocell['oe_mux']['values']['VCC_pt5'] for offset, oe_mux_fuse in enumerate(oe_mux_fuses): assert f_o_sync_d_fast[oe_mux_fuse] == ( oe_mux_gnd >> offset) & 1 f_o_sync_d_fast[oe_mux_fuse] = (oe_mux_vcc >> offset) & 1 # Do bitdiff on all four bitstreams just for consistency checking. bitdiff.describe( 2, { 'o_comb_d_comb': f_o_comb_d_comb, 'o_sync_d_comb': f_o_sync_d_comb, 'o_comb_d_pt2': f_o_comb_d_pt2, 'o_sync_d_fast': f_o_sync_d_fast, }) # Do final bitdiff. macrocell.update({ 'o_mux': bitdiff.describe(1, { 'comb': f_o_comb_d_comb, 'sync': f_o_sync_d_comb }), 'd_mux': bitdiff.describe(1, { 'comb': f_o_comb_d_comb, 'fast': f_o_comb_d_pt2
for macrocell_idx, (macrocell_name, macrocell) in enumerate( device['macrocells'].items()): progress(1) def run(code): return toolchain.run( f"module top(input CLK, output O); " f"wire Q; TRI tri(Q, 1'b0, O); " f"{code} " f"endmodule", { 'CLK': pinout['C1'], 'ff': str(601 + macrocell_idx), }, f"{device_name}-{package}") f_dff = run("DFF ff(.CLK(CLK), .D(1'b0), .Q(Q));") f_tff = run("TFF ff(.CLK(CLK), .T(1'b0), .Q(Q));") # The GND choice of XOR B mux is shared with !PT1 and !PT2 choices: if xor_invert # is off, then it is GND; otherwise: if pt2_mux is xor and xor_a_mux is sum, then # it is !PT2; if pt1_mux is flb and xor_a_mux is VCC_pt2, then it is !PT1; otherwise # it is GND. Further, the XOR B mux is linked to FLB: if XOR B mux is !PT1, then FLB # is always 1, otherwise FLB follows pt1_mux. macrocell.update({ 'xor_b_mux': bitdiff.describe(1, { 'VCC_pt12': f_dff, 'ff_qn': f_tff }) })