Skip to content

Implement the DLLDLY primitive. #318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions apycula/attrids.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
86 changes: 83 additions & 3 deletions apycula/chipdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ def set_banks(fse, db):
15: 'PLL',
39: 'BSRAM_INIT',
49: 'HCLK',
52: 'DLLDLY',
59: 'CFG',
62: 'OSC',
63: 'USB',
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
40 changes: 38 additions & 2 deletions apycula/gowin_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -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*)")

Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions apycula/gowin_unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
Loading