Skip to content

Commit

Permalink
Get the IOB encoding from the vendor tables (#70)
Browse files Browse the repository at this point in the history
* Get the IOB encoding from the vendor tables

The database is populated from tables for the following I/O attributes:
 - PULL_MODE
 - HYSTERESIS
 - SLEW_RATE
 - DRIVE
 - OPEN_DRAIN

The SINGLE_RESISTOR attribute and the LVCMOS12 I/O standard are currently disabled.

Also the unpacking of the IOB is probably broken.

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

* Add support for GW1NS-4 to the IOB table reader

This won't actually allow you to build the base for GW1NS-4 from the tables
by itself --- that requires changes to pindef and a bunch of other places,
but I want to make it easier to merge with pepijndevos' work.

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

* Use the different package for the GW1NS-4 fuzzing

Previously a partnumber was used, which was missing in our chip
database.
For the user the replacement does not matter, but for internal
testing it is critical: I compare the generated images with
the vendor tool and the free one and the packages must match.

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

* Read the tables only for the allowed modes

Modes are used, the correctness of which has already been confirmed
by fuzzing. This is due to the fact that some chips have defective
pins that do not support all IO standards.
Hence the requirement to read tables only after general fuzzing.

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

* Process simplified IO

GW1N-1 ultra simple IO cells, which are IOBUFs only and turn into IBUFs
or OBUFs by connecting the OEN input to VCC or VSS respectively, require
special processing.

Here fuzzig detects the involvement of the routing bits and in this case
marks these cells as a special type of IOBS (iob simplified).

nextpnr can then generate the necessary routes for the OEN input
connection in order to obtain the desired I/O buffer type.

Signed-off-by: YRabbit <[email protected]>
  • Loading branch information
yrabbit authored Dec 24, 2021
1 parent 75fe91e commit 13eedd3
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 116 deletions.
18 changes: 18 additions & 0 deletions apycula/chipdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class Bel:
flags: Dict[Union[int, str], Set[Coord]] = field(default_factory=dict)
# { iostd: { mode : IOBMode}}
iob_flags: Dict[str, Dict[str, IOBMode]] = field(default_factory=dict)
lvcmos121518_bits: Set[Coord] = field(default_factory = set)
# this Bel is IOBUF and needs routing to become IBUF or OBUF
simplified_iob: bool = field(default = False)
# banks
bank_mask: Set[Coord] = field(default_factory=set)
bank_flags: Dict[str, Set[Coord]] = field(default_factory=dict)
Expand Down Expand Up @@ -404,6 +407,14 @@ def dff_clean(dev):
for mode, bits in bel.modes.items():
bits -= extra_bits

def get_route_bits(db, row, col):
""" All routing bits for the cell """
bits = set()
for w in db.grid[row][col].pips.values():
for v in w.values():
bits.update(v)
return bits

def diff2flag(dev):
""" Minimize bits for flag values and calc flag bitmask"""
seen_bels = []
Expand All @@ -414,11 +425,18 @@ def diff2flag(dev):
if not bel.iob_flags or bel in seen_bels:
continue
seen_bels.append(bel)
# get routing bits for cell
rbits = get_route_bits(dev, idx, jdx)
# If for a given mode all possible values of one flag
# contain some bit, then this bit is "noise" --- this bit
# belongs to the default value of another flag. Remove.
for iostd, iostd_rec in bel.iob_flags.items():
for mode, mode_rec in iostd_rec.items():
# if encoding has routing
r_encoding = mode_rec.encode_bits & rbits
mode_rec.encode_bits -= rbits
if r_encoding and mode != 'IOBUF':
bel.simplified_iob = True
mode_rec.decode_bits = mode_rec.encode_bits.copy()
for flag, flag_rec in mode_rec.flags.items():
noise_bits = None
Expand Down
5 changes: 4 additions & 1 deletion apycula/gowin_bba.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ def write_pips(b, pips):
def write_bels(b, bels):
with b.block("bels") as blk:
for typ, bel in bels.items():
b.u16(id_string(typ))
if bel.simplified_iob:
b.u16(id_string(f'{typ}S'))
else:
b.u16(id_string(typ))
with b.block("portmap") as port_blk:
for dest, src in bel.portmap.items():
b.u16(id_string(dest))
Expand Down
35 changes: 31 additions & 4 deletions apycula/gowin_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import re
import pickle
import itertools
import numpy as np
import json
import argparse
Expand Down Expand Up @@ -60,6 +61,13 @@ def infovaluemap(infovalue, start=2):
"SSTL33_II" : "SSTL33_I",
"LVTTL33" : "LVCMOS33",
}
# For each bank, remember the Bels used, mark whether Outs were among them and the standard.
class BankDesc:
def __init__(self, iostd, inputs_only, bels_tiles):
self.iostd = iostd
self.inputs_only = inputs_only
self.bels_tiles = bels_tiles

_banks = {}
_sides = "AB"
def place(db, tilemap, bels, cst, args):
Expand Down Expand Up @@ -98,7 +106,7 @@ def place(db, tilemap, bels, cst, args):
# XXX skip power
if not cellname.startswith('\$PACKER'):
cst.cells[cellname] = (row, col, int(num) // 2, _sides[int(num) % 2])
elif typ == "IOB":
elif typ[:3] == "IOB":
edge = 'T'
idx = col;
if row == db.rows:
Expand All @@ -124,7 +132,7 @@ def place(db, tilemap, bels, cst, args):
pinless_io = False
try:
bank = chipdb.loc2bank(db, row - 1, col - 1)
iostd = _banks.setdefault(bank, None)
iostd = _banks.setdefault(bank, BankDesc(None, True, [])).iostd
except KeyError:
if not args.allow_pinless_io:
raise Exception(f"IO{edge}{idx}{num} is not allowed for a given package")
Expand All @@ -148,11 +156,19 @@ def place(db, tilemap, bels, cst, args):
if not iostd:
iostd = "LVCMOS18"
if not pinless_io:
_banks[bank] = iostd
_banks[bank].iostd = iostd
if mode == 'IBUF':
_banks[bank].bels_tiles.append((iob, tile))
else:
_banks[bank].inputs_only = False

cst.attrs.setdefault(cellname, {}).update({"IO_TYPE": iostd})
# collect flag bits
bits = iob.iob_flags[iostd][mode].encode_bits.copy()
# XXX OPEN_DRAIN must be after DRIVE
attrs_keys = attrs.keys()
if 'OPEN_DRAIN=ON' in attrs_keys:
attrs_keys = itertools.chain(attrs_keys, ['OPEN_DRAIN=ON'])
for flag in attrs.keys():
flag_name_val = flag.split("=")
if len(flag_name_val) < 2:
Expand All @@ -161,6 +177,10 @@ def place(db, tilemap, bels, cst, args):
continue
if flag_name_val[0] == chipdb.mode_attr_sep + "IO_TYPE":
continue
# skip OPEN_DRAIN=OFF can't clear by mask and OFF is the default
if flag_name_val[0] == chipdb.mode_attr_sep + "OPEN_DRAIN" \
and flag_name_val[1] == 'OFF':
continue
# set flag
mode_desc = iob.iob_flags[iostd][mode]
try:
Expand Down Expand Up @@ -191,7 +211,14 @@ def place(db, tilemap, bels, cst, args):
bits |= bank_bel.bank_flags[iostd]
for row, col in bits:
tile[row][col] = 1

# If the entire bank has only inputs, the LVCMOS12/15/18 bit is set
# in each IBUF regardless of the actual I/O standard.
for _, bank_desc in _banks.items():
if bank_desc.inputs_only:
if bank_desc.iostd in {'LVCMOS33', 'LVCMOS25'}:
for bel, tile in bank_desc.bels_tiles:
for row, col in bel.lvcmos121518_bits:
tile[row][col] = 1

def route(db, tilemap, pips):
for row, col, src, dest in pips:
Expand Down
15 changes: 12 additions & 3 deletions apycula/gowin_unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ def zero_bits(mode, all_modes):
m_mask.update(flag.mask)
return res.difference(all_modes[mode].decode_bits).difference(m_mask)

# If the length of the bit pattern is equal, start the comparison with IOBUF
def _io_mode_sort_func(mode):
l = len(mode[1].decode_bits) * 10
if mode[0] == 'IOBUF':
l += 2
elif mode[1] == 'OBUF':
l += 1
return l
# 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.
Expand All @@ -39,6 +47,7 @@ def parse_tile_(db, row, col, tile, default=True, noalias=False, noiostd = True)
bels = {}
for name, bel in tiledata.bels.items():
if name[0:3] == "IOB":
#print(name)
if noiostd:
iostd = ''
else:
Expand All @@ -49,7 +58,7 @@ 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 = lambda m: len(m[1].decode_bits), reverse = True):
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
Expand Down Expand Up @@ -249,7 +258,7 @@ def tile2verilog(dbrow, dbcol, bels, pips, clock_pips, mod, cfg, cst, db):
cst.cells[name] = (row, col, int(idx) // 2, _sides[int(idx) % 2])
make_muxes(row, col, idx, db, mod)
elif typ == "ALU":
print(flags)
#print(flags)
kind, = flags # ALU only have one flag
idx = int(idx)
name = f"R{row}C{col}_ALU_{idx}"
Expand Down Expand Up @@ -277,7 +286,7 @@ def tile2verilog(dbrow, dbcol, bels, pips, clock_pips, mod, cfg, cst, db):
mod.primitives[name] = alu

elif typ == "DFF":
print(flags)
#print(flags)
kind, = flags # DFF only have one flag
idx = int(idx)
port = dffmap[kind]
Expand Down
Loading

0 comments on commit 13eedd3

Please sign in to comment.