Skip to content

Commit

Permalink
Add initial PLL support (#133)
Browse files Browse the repository at this point in the history
* Add initial PLL support

This PLL only works on the GW1N-1 chip and understands only two fixed
frequencies: 52 and 56 MHz.

Dynamic divider tuning is not supported.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

* Add an example for PLL

Only for owners of Tangnano board and oscilloscope.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

* Add unpacker for PLL

At the moment it is not clear how it will behave with large chips with
multiple PLLs and individual parts arranged differently in relation to
each other, but for GW1N-1 it will work reasonably well as a pair to
gowin_pack.

And keep in mind that unpacking images made with vendor IDE will be
wrong in terms of wiring - remember we are currently using routing over
general purpose wires, not special (mostly unexplored) lines.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

* Add support for a more common chip

The GW1N-1 and GW1NZ-1 have a similar PLL, but the board with the former
chip is already very hard to buy, so let's experiment with a more
affordable chip.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

* Add missing files for PLL examples

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
Co-authored-by: Pepijn de Vos <pepijndevos@gmail.com>
  • Loading branch information
yrabbit and pepijndevos authored Nov 14, 2022
1 parent 6cced15 commit bbd4b3d
Show file tree
Hide file tree
Showing 8 changed files with 1,135 additions and 9 deletions.
603 changes: 603 additions & 0 deletions apycula/attrids.py

Large diffs are not rendered by default.

180 changes: 179 additions & 1 deletion apycula/chipdb.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dataclasses import dataclass, field
from typing import Dict, List, Set, Tuple, Union, ByteString
from itertools import chain
import re
from functools import reduce
from collections import namedtuple
Expand Down Expand Up @@ -64,6 +65,10 @@ class Tile:
for this specific tile type"""
width: int
height: int
# At the time of packing/unpacking the information about the types of cells
# is already lost, it is critical to work through the 'logicinfo' table so
# store it.
ttyp: int
# a mapping from dest, source wire to bit coordinates
pips: Dict[str, Dict[str, Set[Coord]]] = field(default_factory=dict)
clock_pips: Dict[str, Dict[str, Set[Coord]]] = field(default_factory=dict)
Expand All @@ -83,6 +88,15 @@ class Device:
cmd_hdr: List[ByteString] = field(default_factory=list)
cmd_ftr: List[ByteString] = field(default_factory=list)
template: np.ndarray = None
# allowable values of bel attributes
# {table_name: [(attr_id, attr_value)]}
logicinfo: Dict[str, List[Tuple[int, int]]] = field(default_factory=dict)
# fuses for a pair of the "features" (or pairs of parameter values)
# {ttype: {table_name: {(feature_A, feature_B): {bits}}}
shortval: Dict[int, Dict[str, Dict[Tuple[int, int], Set[Coord]]]] = field(default_factory=dict)
# fuses for 16 of the "features"
# {ttype: {table_name: {(feature_0, feature_1, ..., feature_15): {bits}}}
longval: Dict[int, Dict[str, Dict[Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int], Set[Coord]]]] = field(default_factory=dict)
# always-connected dest, src aliases
aliases: Dict[Tuple[int, int, str], Tuple[int, int, str]] = field(default_factory=dict)

Expand Down Expand Up @@ -145,6 +159,17 @@ def fse_pips(fse, ttyp, table=2, wn=wirenames):

return pips

# make PLL bels
def fse_pll(device, fse, ttyp):
bels = {}
# XXX use second grid in order to find PLL ttypes
if device in ['GW1N-1', 'GW1NZ-1']:
if ttyp == 89:
bel = bels.setdefault('RPLLB', Bel())
else:
bel = bels.setdefault('RPLLA', Bel())
return bels

# add the ALU mode
# new_mode_bits: string like "0110000010011010"
def add_alu_mode(base_mode, modes, lut, new_alu_mode, new_mode_bits):
Expand Down Expand Up @@ -319,25 +344,145 @@ def set_banks(fse, db):
for rd in fse[ttyp]['longval'][37]:
db.grid[row][col].bels.setdefault(f"BANK{rd[0]}", Bel())

_known_logic_tables = {
8: 'DCS',
9: 'GSR',
10: 'IOLOGIC',
11: 'IOB',
12: 'SLICE',
13: 'BSRAM',
14: 'DSP',
15: 'PLL',
62: 'USB',
}

_known_tables = {
4: 'CONST',
5: 'LUT',
21: 'IOLOGICA',
22: 'IOLOGICB',
23: 'IOBA',
24: 'IOBB',
25: 'CLS0',
26: 'CLS1',
27: 'CLS2',
28: 'CLS3',
35: 'PLL',
37: 'BANK',
40: 'IOBC',
41: 'IOBD',
42: 'IOBE',
43: 'IOBF',
44: 'IOBG',
45: 'IOBH',
46: 'IOBI',
47: 'IOBJ',
53: 'DLLDEL0',
54: 'DLLDEL1',
56: 'DLL0',
64: 'USB',
66: 'EFLASH',
68: 'ADC',
80: 'DLL1',
}

def fse_fill_logic_tables(dev, fse):
# logicinfo
for ltable in fse['header']['logicinfo'].keys():
if ltable in _known_logic_tables.keys():
table = dev.logicinfo.setdefault(_known_logic_tables[ltable], [])
else:
table = dev.logicinfo.setdefault(f"unknown_{ltable}", [])
for attr, val, _ in fse['header']['logicinfo'][ltable]:
table.append((attr, val))
# shortval
ttypes = {t for row in fse['header']['grid'][61] for t in row}
for ttyp in ttypes:
if 'shortval' in fse[ttyp].keys():
ttyp_rec = dev.shortval.setdefault(ttyp, {})
for stable in fse[ttyp]['shortval'].keys():
if stable in _known_tables:
table = ttyp_rec.setdefault(_known_tables[stable], {})
else:
table = ttyp_rec.setdefault(f"unknown_{stable}", {})
for f_a, f_b, *fuses in fse[ttyp]['shortval'][stable]:
table[(f_a, f_b)] = {fuse.fuse_lookup(fse, ttyp, f) for f in unpad(fuses)}
if 'longval' in fse[ttyp].keys():
ttyp_rec = dev.longval.setdefault(ttyp, {})
for ltable in fse[ttyp]['longval'].keys():
if ltable in _known_tables:
table = ttyp_rec.setdefault(_known_tables[ltable], {})
else:
table = ttyp_rec.setdefault(f"unknown_{ltable}", {})
for f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, *fuses in fse[ttyp]['longval'][ltable]:
table[(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15)] = {fuse.fuse_lookup(fse, ttyp, f) for f in unpad(fuses)}

def from_fse(device, fse):
dev = Device()
ttypes = {t for row in fse['header']['grid'][61] for t in row}
tiles = {}
for ttyp in ttypes:
w = fse[ttyp]['width']
h = fse[ttyp]['height']
tile = Tile(w, h)
tile = Tile(w, h, ttyp)
tile.pips = fse_pips(fse, ttyp, 2, wirenames)
tile.clock_pips = fse_pips(fse, ttyp, 38, clknames)
if 5 in fse[ttyp]['shortval']:
tile.bels = fse_luts(fse, ttyp)
if 51 in fse[ttyp]['shortval']:
tile.bels = fse_osc(device, fse, ttyp)
if ttyp in [88, 89]:
tile.bels = fse_pll(device, fse, ttyp)
tiles[ttyp] = tile

fse_fill_logic_tables(dev, fse)
dev.grid = [[tiles[ttyp] for ttyp in row] for row in fse['header']['grid'][61]]
return dev

# get fuses for attr/val set using short/longval table
# returns a bit set
def get_table_fuses(attrs, table):
bits = set()
for key, fuses in table.items():
# all 16 "features" must be present to be able to use a set of bits from the record
have_full_key = True
for attrval in key:
if attrval == 0: # no "feature"
continue
if attrval > 0:
# this "feature" must present
if attrval not in attrs:
have_full_key = False
break
continue
if attrval < 0:
# this "feature" is set by default and can only be unset
if abs(attrval) in attrs:
have_full_key = False
break
if not have_full_key:
continue
bits.update(fuses)
return bits

# get fuses for attr/val set using shortval table for ttyp
# returns a bit set
def get_shortval_fuses(dev, ttyp, attrs, table_name):
return get_table_fuses(attrs, dev.shortval[ttyp][table_name])

# get fuses for attr/val set using longval table for ttyp
# returns a bit set
def get_longval_fuses(dev, ttyp, attrs, table_name):
return get_table_fuses(attrs, dev.longval[ttyp][table_name])

# add the attribute/value pair into an set, which is then passed to
# get_longval_fuses() and get_shortval_fuses()
def add_attr_val(dev, logic_table, attrs, attr, val):
for idx, attr_val in enumerate(dev.logicinfo[logic_table]):
if attr_val[0] == attr and attr_val[1] == val:
attrs.add(idx)
break

def get_pins(device):
if device not in {"GW1N-1", "GW1NZ-1", "GW1N-4", "GW1N-9", "GW1NR-9", "GW1N-9C", "GW1NR-9C", "GW1NS-2", "GW1NS-2C", "GW1NS-4", "GW1NSR-4C"}:
raise Exception(f"unsupported device {device}")
Expand Down Expand Up @@ -425,6 +570,16 @@ def json_pinout(device):
raise Exception("unsupported device")



_pll_inputs = [(5, 'CLKFB'), (6, 'FBDSEL0'), (7, 'FBDSEL1'), (8, 'FBDSEL2'), (9, 'FBDSEL3'),
(10, 'FBDSEL4'), (11, 'FBDSEL5'),
(12, 'IDSEL0'), (13, 'IDSEL1'), (14, 'IDSEL2'), (15, 'IDSEL3'), (16, 'IDSEL4'),
(17, 'IDSEL5'),
(18, 'ODSEL0'), (19, 'ODSEL1'), (20, 'ODSEL2'), (21, 'ODSEL3'), (22, 'ODSEL4'),
(24, 'PSDA0'), (25, 'PSDA1'), (26, 'PSDA2'), (27, 'PSDA3'),
(28, 'DUTYDA0'), (29, 'DUTYDA1'), (30, 'DUTYDA2'), (31, 'DUTYDA3'),
(32, 'FDLY0'), (33, 'FDLY1'), (34, 'FDLY2'), (35, 'FDLY3')]
_pll_outputs = [(0, 'CLKOUT'), (1, 'LOCK'), (2, 'CLKOUTP'), (3, 'CLKOUTD'), (4, 'CLKOUTD3')]
def dat_portmap(dat, dev):
for row in dev.grid:
for tile in row:
Expand All @@ -447,6 +602,28 @@ def dat_portmap(dat, dev):
bel.portmap['I'] = out
oe = wirenames[dat[f'Iobuf{pin}OE']]
bel.portmap['OE'] = oe
elif name.startswith("ODDR"):
d0 = wirenames[dat[f'Iologic{pin}In'][1]]
bel.portmap['D0'] = d0
d1 = wirenames[dat[f'Iologic{pin}In'][2]]
bel.portmap['D1'] = d1
tx = wirenames[dat[f'Iologic{pin}In'][27]]
bel.portmap['TX'] = tx
elif name == 'RPLLA':
for idx, nam in _pll_inputs:
wire = wirenames[dat['PllIn'][idx]]
bel.portmap[nam] = wire
for idx, nam in _pll_outputs:
wire = wirenames[dat['PllOut'][idx]]
bel.portmap[nam] = wire
bel.portmap['CLKIN'] = wirenames[124];
elif name == 'RPLLB':
reset = wirenames[dat['PllIn'][0]]
bel.portmap['RESET'] = reset
reset_p = wirenames[dat['PllIn'][1]]
bel.portmap['RESET_P'] = reset_p
odsel5 = wirenames[dat['PllIn'][23]]
bel.portmap['ODSEL5'] = odsel5

def dat_aliases(dat, dev):
for row in dev.grid:
Expand Down Expand Up @@ -655,3 +832,4 @@ def loc2bank(db, row, col):
bank = db.pin_bank[name + 'B']
return bank


Loading

0 comments on commit bbd4b3d

Please sign in to comment.