Skip to content

Commit

Permalink
Add the long wires (#107)
Browse files Browse the repository at this point in the history
* Add the long wires

It becomes possible to use fast wires that go into each cell of the
chip. To do this, just mark the wire in the .CST file as follows:

CLOCK_LOC "auto_net" BUFS;
CLOCK_LOC "net_with_number" BUFS[6];

the first option is used when the wire number used is indifferent (and
there are only 8 of them), the second option specifies a number.

One can use long wires to OBUF, but other IO primitives will probably
switch to normal routing for now.

See doc/longwires.md

Signed-off-by: YRabbit <[email protected]>

* Use data from the clock fuzzer

Signed-off-by: YRabbit <[email protected]>

* GW1NZ-1 hack

Signed-off-by: YRabbit <[email protected]>

* Separate example for long wires

And also remove unnecessary examples

Signed-off-by: YRabbit <[email protected]>

* Remove the extra CR from Makefile

Signed-off-by: YRabbit <[email protected]>
  • Loading branch information
yrabbit authored Jun 14, 2022
1 parent a090608 commit ec90ea2
Show file tree
Hide file tree
Showing 23 changed files with 429 additions and 225 deletions.
134 changes: 133 additions & 1 deletion apycula/clock_fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from apycula import chipdb
from apycula import fuse_h4x
from apycula import gowin_unpack
from apycula.wirenames import wirenames
from apycula.wirenames import wirenames, clknames, wirenumbers, clknumbers

def dff(mod, cst, row, col, clk=None):
"make a dff with optional clock"
Expand Down Expand Up @@ -307,6 +307,134 @@ def branch_aliases(quads, clks):

return aliases

def get_bufs_bits(fse, ttyp, win, wout):
wi = clknumbers[win]
wo = clknumbers[wout]
fuses = []
for rec in fse[ttyp]['wire'][38]:
if rec[0] == wi and rec[1] == wo:
fuses = chipdb.unpad(rec[2:])
break
return {fuse_h4x.fuse_lookup(fse, ttyp, f) for f in fuses}

# create aliases and pips for long wires
def make_lw_aliases(fse, dat, db, quads, clks):
# type 81, 82, 83, 84 tiles have source muxes
center_row, col82 = dat['center']
center_row -= 1
last_row = db.rows - 1
col82 -= 1
col81 = col82 - 1
col83 = col82 + 1
col84 = col82 + 2
# type 91 and 92 tiles activate the quadrants
# XXX GW1NS-4 have different types
type91 = fse['header']['grid'][61][0][col82]
type92 = fse['header']['grid'][61][last_row][col82]
col91 = col82
has_bottom_quadrants = len(quads) > 2

# quadrants activation bels
# delete direct pips "long wire->spine" because the artificial bel will be used,
# which replaces this direct pip
rows = {(0, 'T', type91)}
if has_bottom_quadrants:
rows.update({ (last_row, 'B', type92) })
for row, half, ttyp in rows:
for idx in range(8):
bel = db.grid[row][col91].bels.setdefault(f'BUFS{idx}', chipdb.Bel())
del db.grid[row][col91].clock_pips[f'LWSPINE{half}L{idx}']
del db.grid[row][col91].clock_pips[f'LWSPINE{half}R{idx}']
src = f'LW{half}{idx}'
db.grid[row][col91].clock_pips.setdefault(f'LWI{idx}', {})[src] = {}
db.grid[row][col91].clock_pips.setdefault(f'LWSPINE{half}L{idx}', {})[f'LWO{idx}'] = {}
bel.flags['L'] = get_bufs_bits(fse, ttyp, f'LW{half}{idx}', f'LWSPINE{half}L{idx}')
db.grid[row][col91].clock_pips.setdefault(f'LWSPINE{half}R{idx}', {})[f'LWO{idx}'] = {}
bel.flags['R'] = get_bufs_bits(fse, ttyp, f'LW{half}{idx}', f'LWSPINE{half}R{idx}')
bel.portmap['I'] = f'LWI{idx}'
bel.portmap['O'] = f'LWO{idx}'
# aliases for long wire origins (center muxes)
# If we have only two quadrants, then do not create aliases in the bottom tile 92,
# thereby excluding these wires from the candidates for routing
if half == 'B' and not has_bottom_quadrants:
continue
if half == 'T':
if idx != 7:
db.aliases.update({(row, col82, src) : (center_row, col82, src)})
else:
db.aliases.update({(row, col82, src) : (center_row, col81, src)})
else:
if idx != 7:
db.aliases.update({(row, col82, src) : (center_row, col83, src)})
else:
db.aliases.update({(row, col82, src) : (center_row, col84, src)})
# branches
# {lw#: {tap_col: {cols}}
taps = {}
lw_taps = [-1, -1, -1, -1]
any_mux = list(clks.keys())[0]
for gclk in range(4):
if gclk not in clks[any_mux].keys():
# XXX
continue
lw_taps[gclk] = min(clks[any_mux][gclk].keys())

if -1 in lw_taps:
# XXX GW1NZ-1 temporary hack
if lw_taps.count(-1) != 1:
raise Exception("Inconsistent clock tap columns, something is went wrong with the clock detection.")
else:
lw_taps[lw_taps.index(-1)] = 0 + 1 + 2 + 3 - 1 - sum(lw_taps)
print(" lw_taps = ", lw_taps)

for lw in range(4):
tap_col = lw_taps[lw]
for col in range(db.cols):
if (col > tap_col + 2) and (tap_col + 4 < db.cols):
tap_col += 4
taps.setdefault(lw, {}).setdefault(tap_col, set()).add(col)

for row in range(db.rows):
for lw, tap_desc in taps.items():
for tap_col, cols in tap_desc.items():
tap_row = 0
if row > (center_row * 2):
tap_row = last_row
db.aliases.update({(row, tap_col, 'LT01') : (tap_row, tap_col, 'LT02')})
db.aliases.update({(row, tap_col, 'LT04') : (tap_row, tap_col, 'LT13')})
for col in cols:
db.aliases.update({(row, col, f'LB{lw}1') : (row, tap_col, f'LBO0')})
db.aliases.update({(row, col, f'LB{lw + 4}1') : (row, tap_col, f'LBO1')})

# tap sources
rows = { (0, 'T') }
if has_bottom_quadrants:
rows.update({ (last_row, 'B') })
for row, qd in rows:
for lw, tap_desc in taps.items():
for tap_col, cols in tap_desc.items():
if tap_col <= col91:
half = 'L'
else:
half = 'R'
db.aliases.update({ (row, tap_col, 'SS00') : (row, col91, f'LWSPINE{qd}{half}{lw}') })
db.aliases.update({ (row, tap_col, 'SS40') : (row, col91, f'LWSPINE{qd}{half}{lw + 4}') })
# XXX remove all pips except SS00 and SS40
pip2keep = {'SS00', 'SS40'}
for tp in ['LT02', 'LT13']:
for pip in [p for p in db.grid[row][tap_col].pips[tp] if p not in pip2keep]:
del db.grid[row][tap_col].pips[tp][pip]
# logic entries
srcs = {}
for i, src in enumerate(dat['UfbIns']):
row, col, pip = src
if pip == 126: # CLK2
db.aliases.update({ (center_row, col82, f'UNK{i + 104}'): (row - 1, col -1, 'CLK2')})
db.aliases.update({ (center_row, col81, f'UNK{i + 104}'): (row - 1, col -1, 'CLK2')})
if has_bottom_quadrants:
db.aliases.update({ (center_row, col83, f'UNK{i + 104}'): (row - 1, col -1, 'CLK2')})
db.aliases.update({ (center_row, col84, f'UNK{i + 104}'): (row - 1, col -1, 'CLK2')})

if __name__ == "__main__":
quads = quadrants()

Expand Down Expand Up @@ -343,6 +471,10 @@ def branch_aliases(quads, clks):
db.aliases.update(ta)
db.aliases.update(ba)

# long wires
make_lw_aliases(fse, dat, db, quads, clks)


with open(f"{tiled_fuzzer.device}_stage2.pickle", 'wb') as f:
pickle.dump(db, f)

10 changes: 9 additions & 1 deletion apycula/gowin_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def sanitize_name(name):

def get_bels(data):
later = []
belre = re.compile(r"R(\d+)C(\d+)_(?:GSR|SLICE|IOB|MUX2_LUT5|MUX2_LUT6|MUX2_LUT7|MUX2_LUT8|ODDR|OSC[ZFH]?)(\w*)")
belre = re.compile(r"R(\d+)C(\d+)_(?:GSR|SLICE|IOB|MUX2_LUT5|MUX2_LUT6|MUX2_LUT7|MUX2_LUT8|ODDR|OSC[ZFH]?|BUFS)(\w*)")
for cellname, cell in data['modules']['top']['cells'].items():
bel = cell['attributes']['NEXTPNR_BEL']
if bel in {"VCC", "GND"}: continue
Expand Down Expand Up @@ -91,6 +91,14 @@ def place(db, tilemap, bels, cst, args):
tile = tilemap[(row-1, col-1)]
if typ == "GSR":
pass
if typ == "BUFS":
# fuses must be reset in order to activate so remove them
bits2zero = set()
for fuses in [fuses for fuses in parms.keys() if fuses in {'L', 'R'}]:
bits2zero.update(tiledata.bels[f'BUFS{num}'].flags[fuses])
for r, c in bits2zero:
tile[r][c] = 0

if typ in {'OSC', 'OSCZ', 'OSCF', 'OSCH'}:
divisor = int(parms['FREQ_DIV'], 2)
if divisor % 2 == 1:
Expand Down
28 changes: 20 additions & 8 deletions apycula/gowin_unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ def _io_mode_sort_func(mode):

# noiostd --- this is the case when the function is called
# with iostd by default, e.g. from the clock fuzzer
# With normal gowun_unpack io standard is determined first and it is known.
# With normal gowin_unpack io standard is determined first and it is known.
def parse_tile_(db, row, col, tile, default=True, noalias=False, noiostd = True):
# TLVDS takes two BUF bels, so skip the B bels.
skip_bels = set()
#print((row, col))
tiledata = db.grid[row][col]
clock_pips = {}
bels = {}
for name, bel in tiledata.bels.items():
if name[0:3] == "IOB":
Expand All @@ -61,15 +62,15 @@ def parse_tile_(db, row, col, tile, default=True, noalias=False, noiostd = True)
# Here we don't use a mask common to all modes (it didn't work),
# instead we try the longest bit sequence first.
for mode, mode_rec in sorted(bel.iob_flags[iostd].items(),
key = _io_mode_sort_func, reverse = True):
# print(mode, mode_rec.decode_bits)
key = _io_mode_sort_func, reverse = True):
#print(mode, mode_rec.decode_bits)
mode_bits = {(row, col)
for row, col in mode_rec.decode_bits
if tile[row][col] == 1}
# print("read", mode_bits)
#print("read", mode_bits)
if mode_rec.decode_bits == mode_bits:
zeros = zero_bits(mode, bel.iob_flags[iostd])
# print("zeros", zeros)
#print("zeros", zeros)
used_bits = {tile[row][col] for row, col in zeros}
if not any(used_bits):
bels.setdefault(name, set()).add(mode)
Expand All @@ -92,7 +93,7 @@ def parse_tile_(db, row, col, tile, default=True, noalias=False, noiostd = True)
for row, col in bel.mode_bits
if tile[row][col] == 1}
#print(name, sorted(bel.mode_bits))
#print("read", sorted(mode_bits))
#print("read mode:", sorted(mode_bits))
for mode, bits in bel.modes.items():
#print(mode, sorted(bits))
if bits == mode_bits and (default or bits):
Expand All @@ -117,6 +118,17 @@ def parse_tile_(db, row, col, tile, default=True, noalias=False, noiostd = True)
if name == "RAM16" and not name in bels:
continue
bels.setdefault(name, set()).add(flag)
# revert BUFS flags
if name.startswith('BUFS'):
flags = bels.get(name, set()) ^ {'R', 'L'}
if flags:
num = name[4:]
half = 'T'
if row != 0:
half = 'B'
for qd in flags:
clock_pips[f'LWSPINE{half}{qd}{num}'] = f'LW{half}{num}'
#print("flags:", sorted(bels.get(name, set())))

pips = {}
for dest, srcs in tiledata.pips.items():
Expand All @@ -129,7 +141,6 @@ def parse_tile_(db, row, col, tile, default=True, noalias=False, noiostd = True)
if bits == used_bits and (default or bits):
pips[dest] = src

clock_pips = {}
for dest, srcs in tiledata.clock_pips.items():
pip_bits = set().union(*srcs.values())
used_bits = {(row, col)
Expand All @@ -139,6 +150,7 @@ def parse_tile_(db, row, col, tile, default=True, noalias=False, noiostd = True)
# only report connection aliased to by a spine
if bits == used_bits and (noalias or (row, col, src) in db.aliases):
clock_pips[dest] = src

return {name: bel for name, bel in bels.items() if name not in skip_bels}, pips, clock_pips


Expand Down Expand Up @@ -268,7 +280,7 @@ def tile2verilog(dbrow, dbcol, bels, pips, clock_pips, mod, cst, db):
mod.wires.update({srcg, destg})
mod.assigns.append((destg, srcg))

belre = re.compile(r"(IOB|LUT|DFF|BANK|CFG|ALU|RAM16|ODDR|OSC[ZFH]?)(\w*)")
belre = re.compile(r"(IOB|LUT|DFF|BANK|CFG|ALU|RAM16|ODDR|OSC[ZFH]?|BUFS)(\w*)")
if have_iologic(bels):
bels_items = move_iologic(bels)
else:
Expand Down
12 changes: 5 additions & 7 deletions apycula/tiled_fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from apycula import bslib
from apycula import pindef
from apycula import fuse_h4x
from apycula.wirenames import wirenames, clknames
from apycula.wirenames import wirenames, clknames, wirenumbers, clknumbers
#TODO proper API
#from apycula import dat19_h4x
from apycula import tm_h4x
Expand Down Expand Up @@ -118,7 +118,7 @@ def recode_idx_gw1nz_1(idx):
"package": "UBGA332",
"device": "GW1N-9C-UBGA332-6",
"partnumber": "GW1N-LV9UG332C6/I5",
"recode_idx": recode_idx_gw1n9, # TODO: recheck
"recode_idx": recode_idx_gw1n9,
},
"GW1N-4": {
"package": "PBGA256",
Expand All @@ -136,10 +136,12 @@ def recode_idx_gw1nz_1(idx):
"package": "QFN48",
"device": "GW1NZ-1-QFN48-6",
"partnumber": "GW1NZ-LV1QN48C6/I5",
"recode_idx": recode_idx_gw1nz_1, # TODO: check
"recode_idx": recode_idx_gw1nz_1,
},
}[device]



name_idx = 0
def make_name(bel, typ):
global name_idx
Expand Down Expand Up @@ -1117,10 +1119,6 @@ def run_pnr(mod, constr, config):
diff_cap_info = pindef.get_diff_cap_info(device, params['package'], True)
fse_diff_iob(fse, db, pin_locations, diff_cap_info);

# diff IOB
diff_cap_info = pindef.get_diff_cap_info(device, params['package'], True)
fse_diff_iob(fse, db, pin_locations, diff_cap_info);

# bank modes
fse_banks(fse, db, corners)

Expand Down
10 changes: 9 additions & 1 deletion apycula/wirenames.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,17 @@
279: "LT00", 280: "LT10", 281: "LT20", 282: "LT30", 283: "LT02", 284: "LT13", 285: "LT01", 286: "LT04", 287: "LBO0", 288: "LBO1", 289: "SS00", 290: "SS40",
291: "GT00", 292: "GT10", 293: "GBO0", 294: "GBO1", 295: "DI0", 296: "DI1", 297: "DI2", 298: "DI3", 299: "DI4", 300: "DI5", 301: "DI6", 302: "DI7",
303: "CIN0", 304: "CIN1", 305: "CIN2", 306: "CIN3", 307: "CIN4", 308: "CIN5", 309: "COUT0", 310: "COUT1", 311: "COUT2", 312: "COUT3", 313: "COUT4", 314: "COUT5"}
wirenames.update({n: f"LWSPINETL{n - 1001}" for n in range(1001, 1009)})
wirenames.update({n: f"LWSPINETR{n - 1009}" for n in range(1009, 1017)})
wirenames.update({n: f"LWSPINEBL{n - 1033}" for n in range(1033, 1041)})
wirenames.update({n: f"LWSPINEBR{n - 1041}" for n in range(1041, 1049)})

wirenumbers = {v: k for k, v in wirenames.items()}

clknames = wirenames.copy()
clknames.update({n: f"SPINE{n}" for n in range(32)})
clknames.update({n: f"UNK{n}" for n in range(32, 261)})
clknames.update({n: f"LWT{n - 32}" for n in range(32, 40)})
clknames.update({n: f"LWB{n - 40}" for n in range(40, 48)})
clknames.update({n: f"UNK{n}" for n in range(48, 261)})

clknumbers = {v: k for k, v in clknames.items()}
Loading

0 comments on commit ec90ea2

Please sign in to comment.