diff --git a/apycula/attrids.py b/apycula/attrids.py index d1efbfdd..1d74115b 100644 --- a/apycula/attrids.py +++ b/apycula/attrids.py @@ -916,6 +916,30 @@ 'VCC': 22, } +# DLLDLY +dlldly_attrids = { + 'ENABLED': 0, + 'LOADN': 2, + 'SIGN': 3, + 'MODE': 4, + 'ADJ0': 5, + 'ADJ1': 6, + 'ADJ2': 7, + 'ADJ3': 8, + 'ADJ4': 9, + 'ADJ5': 10, + 'ADJ6': 11, + 'ADJ7': 12, + } + +dlldly_attrvals = { + 'UNKNOWN': 0, + 'ENABLE': 1, + 'NORMAL': 3, + '1': 4, + 'NEG': 5, + } + # DLL dll_attrids = { 'CLKSEL': 0, diff --git a/apycula/chipdb.py b/apycula/chipdb.py index 731361f8..402af0f3 100644 --- a/apycula/chipdb.py +++ b/apycula/chipdb.py @@ -477,6 +477,7 @@ def set_banks(fse, db): 15: 'PLL', 39: 'BSRAM_INIT', 49: 'HCLK', + 52: 'DLLDLY', 59: 'CFG', 62: 'OSC', 63: 'USB', @@ -1438,6 +1439,64 @@ def fse_create_dhcen(dev, device, fse, dat: Datfile): hclkin.update({ 'ce' : wire}) dhcen.append(hclkin) +# DLLDLY +# from Gowin doc "DLLDLY is the clock delay module that adjusts the input clock according to the DLLSTEP" +# In practice the following peculiarities were discovered: the input for the +# clock cannot be arbitrary things, but only specialised pins of the chip and +# the delay line is cut in between the pin and the clock MUX. +# { bel_loc : ([('io_loc', 'io_output_wire', (row, col, flag_wirea))], [(fuse_row, fuse_col)]) + +_dlldly = { + 'GW1N-1': { + (10, 19) : { + 'fuse_bels': {(10, 0), (10, 19)}, + 'ios' : [('X9Y10', 'IOBA', (10, 0, 'F1')), ('X10Y10', 'IOBA', (10, 0, 'F0'))], + }, + }, + 'GW1NZ-1': { + ( 0, 19) : { + 'fuse_bels': {(0, 5)}, + 'ios' : [('X9Y0', 'IOBA', (0, 19, 'F1')), ('X10Y0', 'IOBA', (0, 19, 'F0'))], + }, + (10, 19) : { + 'fuse_bels' : {(5, 19)}, + 'ios' : [('X19Y4', 'IOBA', (5, 19, 'F0')), ('X19Y6', 'IOBA', (5, 19, 'F2'))], + }, + } + } + +def fse_create_dlldly(dev, device): + if device in _dlldly: + for bel, fuse_ios in _dlldly[device].items(): + row, col = bel + fuse_bels = fuse_ios['fuse_bels'] + ios = fuse_ios['ios'] + extra = dev.extra_func.setdefault((row, col), {}) + dlldly = extra.setdefault(f'dlldly', {}) + for idx in range(2): + dlldly[idx] = {'io_loc': ios[idx][0], 'io_bel': ios[idx][1]} + # FLAG output + nodename = f'X{col}Y{row}/DLLDLY_FLAG{idx}' + nodename = add_node(dev, nodename, "", row, col, f'DLLDLY_FLAG{idx}') + add_node(dev, nodename, "", ios[idx][2][0], ios[idx][2][1], ios[idx][2][2]) + add_node(dev, f'{ios[idx][0]}/DLLDLY_IN', "TILE_CLK", row, col, f'DLLDLY_CLKIN{idx}') + add_node(dev, f'{ios[idx][0]}/DLLDLY_OUT', "DLLDLY_O", row, col, f'DLLDLY_CLKOUT{idx}') + + # STEP wires + wires = dlldly[idx].setdefault('in_wires', {}) + prefix = ["CB", "DC"][idx] + for wire_idx in range(8): + wires[f'DLLSTEP{wire_idx}'] = f"{prefix[wire_idx // 4]}{(wire_idx + 4) % 8}" + wires['DIR'] = ["A1", "B4"][idx] + wires['LOADN'] = ["A0", "B7"][idx] + wires['MOVE'] = ["B6", "B5"][idx] + wires['CLKIN'] = f'DLLDLY_CLKIN{idx}' + + wires = dlldly[idx].setdefault('out_wires', {}) + wires['FLAG'] = f'DLLDLY_FLAG{idx}' + wires['CLKOUT'] = f'DLLDLY_CLKOUT{idx}' + dlldly_bels = extra.setdefault(f'dlldly_fusebels', set()) + dlldly_bels.update(fuse_bels) _pll_loc = { 'GW1N-1': @@ -1685,6 +1744,19 @@ def fse_create_clocks(dev, device, dat: Datfile, fse): add_node(dev, f'{clknames[clk_idx]}-9C', "GLOBAL_CLK", row, dev.cols - 1, 'LWT6') else: add_node(dev, f'{clknames[clk_idx]}-9C', "GLOBAL_CLK", row, 0, 'LWT6') + elif (device == 'GW1NZ-1' and (row == 0 or col == dev.cols - 1)) or (device == 'GW1N-1' and row == dev.rows - 1): + # Do not connect the IO output to the clock node because DLLDLY + # may be located at these positions, which, if used, will be + # the source for the clock. However, if DLLDLY is not used + # (mostly), we need to have a way to connect them - for this we + # add two PIPs - one to connect the IO output to the clock and + # one to connect the IO output to the DLLDLY input. + # Both are non-fuseable, but allow the router to work. + add_node(dev, clknames[clk_idx], "GLOBAL_CLK", row, col, 'PCLK_DUMMY') + dev.grid[row][col].pips['PCLK_DUMMY'] = {wirenames[wire_idx]: set(), 'DLLDLY_OUT': set()} + add_node(dev, f'X{col}Y{row}/DLLDLY_OUT', "DLLDLY_O", row, col, 'DLLDLY_OUT') + add_node(dev, f'X{col}Y{row}/DLLDLY_IN', "TILE_CLK", row, col, 'DLLDLY_IN') + dev.grid[row][col].pips['DLLDLY_IN'] = {wirenames[wire_idx]: set()} else: add_node(dev, clknames[clk_idx], "GLOBAL_CLK", row, col, wirenames[wire_idx]) add_buf_bel(dev, row, col, wirenames[wire_idx]) @@ -1955,8 +2027,16 @@ def create_segments(dev, device): if (b_row, s_col, seg['bottom_gate_wire'][1]) in dev_desc['reserved_wires']: seg['bottom_gate_wire'][1] = None + # remove isolated segments (these are in the DSP area of -9, -9C, -18, -18C) + if (not seg['top_gate_wire'][0] and not seg['top_gate_wire'][1] + and not seg['bottom_gate_wire'][0] and not seg['bottom_gate_wire'][1]): + del dev.segments[(top_gate_row, s_col, seg_idx)] + # new segment i + 1 seg_idx += 4 + # XXX 6 and 7 need static DCS, disable for now + if seg_idx in [6, 7]: + continue seg_1 = dev.segments.setdefault((top_gate_row, s_col, seg_idx), {}) # controlled area seg_1['min_x'] = seg['min_x'] @@ -1990,9 +2070,6 @@ def create_segments(dev, device): seg_1['bottom_gate_wire'][1] = None # remove isolated segments (these are in the DSP area of -9, -9C, -18, -18C) - if (not seg['top_gate_wire'][0] and not seg['top_gate_wire'][1] - and not seg['bottom_gate_wire'][0] and not seg['bottom_gate_wire'][1]): - del dev.segments[(top_gate_row, s_col, seg_idx - 4)] if (not seg_1['top_gate_wire'][0] and not seg_1['top_gate_wire'][1] and not seg_1['bottom_gate_wire'][0] and not seg_1['bottom_gate_wire'][1]): del dev.segments[(top_gate_row, s_col, seg_idx)] @@ -2721,6 +2798,7 @@ def from_fse(device, fse, dat: Datfile): fse_create_emcu(dev, device, dat) fse_create_logic2clk(dev, device, dat) fse_create_dhcen(dev, device, fse, dat) + fse_create_dlldly(dev, device) create_segments(dev, device) disable_plls(dev, device) sync_extra_func(dev) @@ -4131,6 +4209,8 @@ def fse_wire_delays(db): db.wire_delay[clknames[i]] = "CENT_SPINE_PCLK" for i in range(1000, 1010): # HCLK db.wire_delay[clknames[i]] = "X0" # XXX + for wire in {'DLLDLY_OUT', 'DLLDLY_CLKOUT', 'DLLDLY_CLKOUT0', 'DLLDLY_CLKOUT1'}: + db.wire_delay[wire] = "X0" # XXX # assign pads with plls # for now use static table and store the bel name although it is always PLL without a number diff --git a/apycula/gowin_pack.py b/apycula/gowin_pack.py index 36ceba17..3db4e97a 100644 --- a/apycula/gowin_pack.py +++ b/apycula/gowin_pack.py @@ -201,7 +201,7 @@ def get_bits(init_data): def get_bels(data): later = [] if is_himbaechel: - belre = re.compile(r"X(\d+)Y(\d+)/(?:GSR|LUT|DFF|IOB|MUX|ALU|ODDR|OSC[ZFHWO]?|BUF[GS]|RAM16SDP4|RAM16SDP2|RAM16SDP1|PLL|IOLOGIC|CLKDIV2|CLKDIV|BSRAM|ALU|MULTALU18X18|MULTALU36X18|MULTADDALU18X18|MULT36X36|MULT18X18|MULT9X9|PADD18|PADD9|BANDGAP|DQCE|DCS|USERFLASH|EMCU|DHCEN|MIPI_OBUF|MIPI_IBUF)(\w*)") + belre = re.compile(r"X(\d+)Y(\d+)/(?:GSR|LUT|DFF|IOB|MUX|ALU|ODDR|OSC[ZFHWO]?|BUF[GS]|RAM16SDP4|RAM16SDP2|RAM16SDP1|PLL|IOLOGIC|CLKDIV2|CLKDIV|BSRAM|ALU|MULTALU18X18|MULTALU36X18|MULTADDALU18X18|MULT36X36|MULT18X18|MULT9X9|PADD18|PADD9|BANDGAP|DQCE|DCS|USERFLASH|EMCU|DHCEN|MIPI_OBUF|MIPI_IBUF|DLLDLY)(\w*)") else: belre = re.compile(r"R(\d+)C(\d+)_(?:GSR|SLICE|IOB|MUX2_LUT5|MUX2_LUT6|MUX2_LUT7|MUX2_LUT8|ODDR|OSC[ZFHWO]?|BUFS|RAMW|rPLL|PLLVR|IOLOGIC)(\w*)") @@ -2038,6 +2038,34 @@ def set_osc_attrs(db, typ, params): add_attr_val(db, 'OSC', fin_attrs, attrids.osc_attrids[attr], val) return fin_attrs +def set_dlldly_attrs(db, typ, params, cell): + dlldly_attrs = dict() + dlldly_attrs['DLL_INSEL'] = params.get('DLL_INSEL', "1") + dlldly_attrs['DLY_SIGN'] = params.get('DLY_SIGN', "0") + dlldly_attrs['DLY_ADJ'] = params.get('DLY_ADJ', "00000000000000000000000000000000") + + if dlldly_attrs['DLL_INSEL'] != '1': + raise Exception(f"DLL_INSEL parameter values other than 1 are not supported") + dlldly_attrs.pop('DLL_INSEL') + dlldly_attrs['ENABLED'] = 'ENABLE' + dlldly_attrs['MODE'] = 'NORMAL' + + if dlldly_attrs['DLY_SIGN'] == '1': + dlldly_attrs['SIGN'] = 'NEG' + dlldly_attrs.pop('DLY_SIGN') + + for i, ch in enumerate(dlldly_attrs['DLY_ADJ'][-1::-1]): + if ch == '1': + dlldly_attrs[f'ADJ{i}'] = '1' + dlldly_attrs.pop('DLY_ADJ') + + fin_attrs = set() + for attr, val in dlldly_attrs.items(): + if isinstance(val, str): + val = attrids.dlldly_attrvals[val] + add_attr_val(db, 'DLLDLY', fin_attrs, attrids.dlldly_attrids[attr], val) + return fin_attrs + _wire2attr_val = { 'HCLK_IN0': ('HSB0MUX0_HSTOP', 'HCLKCIBSTOP0'), 'HCLK_IN1': ('HSB1MUX0_HSTOP', 'HCLKCIBSTOP2'), @@ -2642,7 +2670,15 @@ def place(db, tilemap, bels, cst, args): cfg_tile = tilemap[(0, 37)] for r, c in bits: cfg_tile[r][c] = 1 - elif typ == "DHCEN": + elif typ == 'DLLDLY': + dlldly_attrs = set_dlldly_attrs(db, typ, parms, cell) + for dlldly_row, dlldly_col in db.extra_func[row - 1, col -1]['dlldly_fusebels']: + dlldly_tiledata = db.grid[dlldly_row][dlldly_col] + dlldly_tile = tilemap[(dlldly_row, dlldly_col)] + bits = get_long_fuses(db, dlldly_tiledata.ttyp, dlldly_attrs, f'DLLDEL{num}') + for r, c in bits: + dlldly_tile[r][c] = 1 + elif typ == 'DHCEN': if 'DHCEN_USED' not in attrs: continue # DHCEN as such is just a control wire and does not have a fuse diff --git a/apycula/gowin_unpack.py b/apycula/gowin_unpack.py index 089e21fb..09c40c00 100644 --- a/apycula/gowin_unpack.py +++ b/apycula/gowin_unpack.py @@ -338,6 +338,16 @@ def parse_tile_(db, row, col, tile, default=True, noalias=False, noiostd = True) # if attrvals: # print(row, col, attrvals) + #if tiledata.ttyp in db.longfuses: + # if 'DLLDEL0' in db.longfuses[tiledata.ttyp].keys(): + # attrvals =parse_attrvals(tile, db.logicinfo['DLLDLY'], db.longfuses[tiledata.ttyp]['DLLDEL0'], attrids.dlldly_attrids) + # if attrvals: + # print(row, col, attrvals) + # if 'DLLDEL1' in db.longfuses[tiledata.ttyp].keys(): + # attrvals =parse_attrvals(tile, db.logicinfo['DLLDLY'], db.longfuses[tiledata.ttyp]['DLLDEL1'], attrids.dlldly_attrids) + # if attrvals: + # print(row, col, attrvals) + clock_pips = {} bels = {} for name, bel in tiledata.bels.items():