Skip to content

Commit

Permalink
Add support for GW1NR-9C PLL
Browse files Browse the repository at this point in the history
 * Both PLLs with all attributes are supported;
 * Static and dynamic setting of generated frequencies;
 * New approach to handling multi-cell primitives: now all ports
   scattered over cells are represented by aliases to the main cell,
   auxiliary cells are simply ignored in nextpnr;
 * A mechanism for describing ports located in auxiliary cells was
   discovered.

Signed-off-by: YRabbit <[email protected]>
  • Loading branch information
yrabbit authored and pepijndevos committed Jan 28, 2023
1 parent 55cc70c commit 6d9ca9c
Show file tree
Hide file tree
Showing 13 changed files with 403 additions and 91 deletions.
79 changes: 53 additions & 26 deletions apycula/chipdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,18 @@ def fse_alonenode(fse, ttyp, table = 6):
def fse_pll(device, fse, ttyp):
bels = {}
if device in {'GW1N-1', 'GW1NZ-1'}:
if ttyp == 89:
bel = bels.setdefault('RPLLB', Bel())
else:
if ttyp == 88:
bel = bels.setdefault('RPLLA', Bel())
elif ttyp == 89:
bel = bels.setdefault('RPLLB', Bel())
elif device in {'GW1NS-4'}:
if ttyp in {88, 89}:
bel = bels.setdefault('PLLVR', Bel())
elif device in {'GW1N-9C'}:
if ttyp in {86, 87}:
bel = bels.setdefault('RPLLA', Bel())
elif ttyp in {74, 75, 76, 77, 78, 79}:
bel = bels.setdefault('RPLLB', Bel())
return bels

# add the ALU mode
Expand Down Expand Up @@ -444,19 +449,25 @@ def fse_fill_logic_tables(dev, fse):
{'TRPLL0CLK0': (0, 17, 'F4'), 'TRPLL0CLK1': (0, 17, 'F5'),
'TRPLL0CLK2': (0, 17, 'F6'), 'TRPLL0CLK3': (0, 17, 'F7'), },
'GW1NS-4':
{'TLPLL0CLK0': (0, 27, 'F4'), 'TLPLL0CLK1': (0, 27, 'F5'),
'TLPLL0CLK2': (0, 27, 'F6'), 'TLPLL0CLK3': (0, 27, 'F7'),
'TRPLL0CLK0': (0, 36, 'F4'), 'TRPLL0CLK1': (0, 36, 'F5'),
'TRPLL0CLK2': (0, 36, 'F6'), 'TRPLL0CLK3': (0, 36, 'F7'), },
{'TLPLL0CLK0': (0, 27, 'F4'), 'TLPLL0CLK1': (0, 27, 'F7'),
'TLPLL0CLK2': (0, 27, 'F6'), 'TLPLL0CLK3': (0, 27, 'F5'),
'TRPLL0CLK0': (0, 36, 'F4'), 'TRPLL0CLK1': (0, 36, 'F7'),
'TRPLL0CLK2': (0, 36, 'F6'), 'TRPLL0CLK3': (0, 36, 'F5'), },
'GW1N-9C':
{'TLPLL0CLK0': (9, 2, 'F4'), 'TLPLL0CLK1': (9, 2, 'F7'),
'TLPLL0CLK2': (9, 2, 'F5'), 'TLPLL0CLK3': (9, 2, 'F6'),
'TRPLL0CLK0': (9, 44, 'F4'), 'TRPLL0CLK1': (9, 44, 'F7'),
'TRPLL0CLK2': (9, 44, 'F5'), 'TRPLL0CLK3': (9, 44, 'F6'), },
}

def fse_create_pll_clock_aliases(db, device):
# we know exactly where the PLL is and therefore know which aliases to create
for row in range(db.rows):
for col in range(db.cols):
for w_dst, w_srcs in db.grid[row][col].clock_pips.items():
for w_src in w_srcs.keys():
# XXX
if device in {'GW1N-1', 'GW1NZ-1', 'GW1NS-4'}:
if device in {'GW1N-1', 'GW1NZ-1', 'GW1NS-4', 'GW1N-9C'}:
if w_src in _pll_loc[device].keys():
db.aliases[(row, col, w_src)] = _pll_loc[device][w_src]

Expand All @@ -476,7 +487,7 @@ def from_fse(device, fse):
if 51 in fse[ttyp]['shortval']:
tile.bels = fse_osc(device, fse, ttyp)
# XXX GW1N(Z)-1 and GW1NS-4 for now
if ttyp in [88, 89]:
if ttyp in [74, 75, 76, 77, 78, 79, 86, 87, 88, 89]:
tile.bels = fse_pll(device, fse, ttyp)
tiles[ttyp] = tile

Expand Down Expand Up @@ -622,6 +633,7 @@ def json_pinout(device):
(12, 'IDSEL0'), (13, 'IDSEL1'), (14, 'IDSEL2'), (15, 'IDSEL3'), (16, 'IDSEL4'),
(17, 'IDSEL5'),
(18, 'ODSEL0'), (19, 'ODSEL1'), (20, 'ODSEL2'), (21, 'ODSEL3'), (22, 'ODSEL4'),
(23, 'ODSEL5'), (0, 'RESET'), (1, 'RESET_P'),
(24, 'PSDA0'), (25, 'PSDA1'), (26, 'PSDA2'), (27, 'PSDA3'),
(28, 'DUTYDA0'), (29, 'DUTYDA1'), (30, 'DUTYDA2'), (31, 'DUTYDA3'),
(32, 'FDLY0'), (33, 'FDLY1'), (34, 'FDLY2'), (35, 'FDLY3')]
Expand Down Expand Up @@ -656,20 +668,41 @@ def dat_portmap(dat, dev, device):
tx = wirenames[dat[f'Iologic{pin}In'][27]]
bel.portmap['TX'] = tx
elif name == 'RPLLA':
# The PllInDlt table seems to indicate in which cell the
# inputs are actually located.
offx = 1
if device in {'GW1N-9C'}:
# two mirrored PLLs
if col > dat['center'][1] - 1:
offx = -1
for idx, nam in _pll_inputs:
wire = wirenames[dat['PllIn'][idx]]
bel.portmap[nam] = wire
off = dat['PllInDlt'][idx] * offx
if off == 0:
bel.portmap[nam] = wire
else:
# not our cell, make an alias
bel.portmap[nam] = f'rPLL{nam}{wire}'
dev.aliases[row, col, f'rPLL{nam}{wire}'] = (row, col + off, wire)
for idx, nam in _pll_outputs:
wire = wirenames[dat['PllOut'][idx]]
off = dat['PllOutDlt'][idx] * offx
if off == 0:
bel.portmap[nam] = wire
else:
# not our cell, make an alias
bel.portmap[nam] = f'rPLL{nam}{wire}'
dev.aliases[row, col, f'rPLL{nam}{wire}'] = (row, col + off, wire)
# clock input
nam = 'CLKIN'
wire = wirenames[dat['PllClkin'][1][0]]
off = dat['PllClkin'][1][1] * offx
if off == 0:
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
else:
# not our cell, make an alias
bel.portmap[nam] = f'rPLL{nam}{wire}'
dev.aliases[row, col, f'rPLL{nam}{wire}'] = (row, col + off, wire)
elif name == 'PLLVR':
pll_idx = 0
if col != 27:
Expand All @@ -686,19 +719,13 @@ def dat_portmap(dat, dev, device):
# you should keep in mind that nextpnr is designed
# so that it will not use such aliases. They have
# to be taken care of separately.
bel.portmap[nam] = f'PLLVR{wire}'
dev.aliases[row, col, f'PLLVR{wire}'] = (9, 37, wire)
bel.portmap[nam] = f'PLLVR{nam}{wire}'
dev.aliases[row, col, f'PLLVR{nam}{wire}'] = (9, 37, wire)
for idx, nam in _pll_outputs:
wire = wirenames[dat[f'SpecPll{pll_idx}Outs'][idx * 3 + 2]]
bel.portmap[nam] = wire
bel.portmap['CLKIN'] = wirenames[124];
reset = wirenames[dat[f'SpecPll{pll_idx}Ins'][0 + 2]]
bel.portmap['RESET'] = reset
reset_p = wirenames[dat[f'SpecPll{pll_idx}Ins'][1 * 3 + 2]]
bel.portmap['RESET_P'] = reset_p
odsel5 = wirenames[dat[f'SpecPll{pll_idx}Ins'][23 * 3 + 2]]
bel.portmap['ODSEL5'] = f'PLLVR{odsel5}'
dev.aliases[row, col, f'PLLVR{odsel5}'] = (9, 37, odsel5)
# VREN pin is placed in another cell
if pll_idx == 0:
vren = 'D0'
Expand Down
2 changes: 2 additions & 0 deletions apycula/clock_fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ def add_rim(rows, cols, spine_row):
rows.add(0)
if tiled_fuzzer.device.startswith("GW1N-9"):
rows.add(9)
else:
rows.add(spine_row - 1)
if max(rows) > spine_row and spine_row != 1:
rows.update({row for row in range(max(rows) + 1, db.rows)})
if tiled_fuzzer.device.startswith("GW1N-9"):
Expand Down
26 changes: 23 additions & 3 deletions apycula/gowin_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,23 @@ def sanitize_name(name):
return retname
return f"\{retname} "

def extra_pll_bels(cell, row, col, num, cellname):
# rPLL can occupy several cells, add them depending on the chip
offx = 1;
if device == 'GW1N-9C':
if int(col) > 28:
offx = -1
for off in [1, 2, 3]:
yield ('RPLLB', int(row), int(col) + offx * off, num,
cell['parameters'], cell['attributes'], sanitize_name(cellname) + f'B{off}')
elif device in {'GW1N-1', 'GW1NZ-1'}:
for off in [1]:
yield ('RPLLB', int(row), int(col) + offx * off, num,
cell['parameters'], cell['attributes'], sanitize_name(cellname) + f'B{off}')

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]?|BUFS|RAMW|RPLL[AB]|PLLVR)(\w*)")
belre = re.compile(r"R(\d+)C(\d+)_(?:GSR|SLICE|IOB|MUX2_LUT5|MUX2_LUT6|MUX2_LUT7|MUX2_LUT8|ODDR|OSC[ZFH]?|BUFS|RAMW|rPLL|PLLVR)(\w*)")
for cellname, cell in data['modules']['top']['cells'].items():
if cell['type'].startswith('DUMMY_') :
continue
Expand All @@ -51,7 +65,11 @@ def get_bels(data):
if 'DIFF' in cell['attributes'].keys():
later.append((cellname, cell, row, col, num))
continue
yield (cell['type'], int(row), int(col), num,
cell_type = cell['type']
if cell_type == 'rPLL':
cell_type = 'RPLLA'
yield from extra_pll_bels(cell, row, col, num, cellname)
yield (cell_type, int(row), int(col), num,
cell['parameters'], cell['attributes'], sanitize_name(cellname))

# diff iobs
Expand Down Expand Up @@ -507,7 +525,9 @@ def place(db, tilemap, bels, cst, args):
tile[r][c] = 1
elif typ.startswith('RPLL'):
pll_attrs = set_pll_attrs(db, 'RPLL', 0, parms)
bits = get_shortval_fuses(db, tiledata.ttyp, pll_attrs, 'PLL')
bits = set()
if 'PLL' in db.shortval[tiledata.ttyp].keys():
bits = get_shortval_fuses(db, tiledata.ttyp, pll_attrs, 'PLL')
#print(typ, bits)
for r, c in bits:
tile[r][c] = 1
Expand Down
37 changes: 26 additions & 11 deletions apycula/gowin_unpack.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from apycula.bslib import read_bitstream
from apycula.wirenames import wirenames

_device = ""
_pinout = ""
_packages = {
'GW1N-1' : 'LQFP144', 'GW1NZ-1' : 'QFN48', 'GW1N-4' : 'PBGA256', 'GW1N-9C' : 'UBGA332',
Expand Down Expand Up @@ -161,7 +162,13 @@ def is_pos_key(key):
# GW1N(Z)-1
def get_pll_A(db, row, col, typ):
if typ == 'B':
col -= 1
if _device in {"GW1N-9C"}:
if col > 28:
col = db.cols - 1
else:
col = 0
else:
col -= 1
return row, col, 'A'

# noiostd --- this is the case when the function is called
Expand All @@ -178,17 +185,18 @@ def parse_tile_(db, row, col, tile, default=True, noalias=False, noiostd = True)
for name, bel in tiledata.bels.items():
if name.startswith("RPLL"):
idx = _pll_cells.setdefault(get_pll_A(db, row, col, name[4]), len(_pll_cells))
attrvals = pll_attrs_refine(parse_attrvals(tile, db.logicinfo['PLL'], db.shortval[tiledata.ttyp]['PLL'], pll_attrids))
modes = set()
for attrval in attrvals:
modes.add(attrval)
modes = { f'DEVICE="{_device}"' }
if 'PLL' in db.shortval[tiledata.ttyp].keys():
attrvals = pll_attrs_refine(parse_attrvals(tile, db.logicinfo['PLL'], db.shortval[tiledata.ttyp]['PLL'], pll_attrids))
for attrval in attrvals:
modes.add(attrval)
if modes:
bels[f'{name}{idx}'] = modes
continue
if name == "PLLVR":
idx = _pll_cells.setdefault(get_pll_A(db, row, col, 'A'), len(_pll_cells))
attrvals = pll_attrs_refine(parse_attrvals(tile, db.logicinfo['PLL'], db.shortval[tiledata.ttyp]['PLL'], pll_attrids))
modes = set()
modes = { f'DEVICE="{_device}"' }
for attrval in attrvals:
modes.add(attrval)
if modes:
Expand Down Expand Up @@ -743,25 +751,32 @@ def main():

args = parser.parse_args()

device = args.device
global _device
_device = args.device
# For tool integration it is allowed to pass a full part number
m = re.match("GW1N(S?)[A-Z]*-(LV|UV|UX)([0-9])C?([A-Z]{2}[0-9]+P?)(C[0-9]/I[0-9])", device)
m = re.match("GW1N(S?)[A-Z]*-(LV|UV|UX)([0-9])C?([A-Z]{2}[0-9]+P?)(C[0-9]/I[0-9])", _device)
if m:
mods = m.group(1)
luts = m.group(3)
device = f"GW1N{mods}-{luts}"
_device = f"GW1N{mods}-{luts}"

with importlib.resources.open_binary("apycula", f"{device}.pickle") as f:
with importlib.resources.open_binary("apycula", f"{_device}.pickle") as f:
db = pickle.load(f)

global _pinout
_pinout = db.pinout[device][_packages[device]]
_pinout = db.pinout[_device][_packages[_device]]

bitmap = read_bitstream(args.bitstream)[0]
bm = chipdb.tile_bitmap(db, bitmap)
mod = codegen.Module()
cst = codegen.Constraints()

# XXX this PLLs have empty main cell
if _device in {'GW1N-9C'}:
bm_pll = chipdb.tile_bitmap(db, bitmap, empty = True)
bm[(9, 0)] = bm_pll[(9, 0)]
bm[(9, 46)] = bm_pll[(9, 46)]

for (drow, dcol, dname), (srow, scol, sname) in db.aliases.items():
src = f"R{srow+1}C{scol+1}_{sname}"
dest = f"R{drow+1}C{dcol+1}_{dname}"
Expand Down
23 changes: 17 additions & 6 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ all: attosoc-tec0117.fs nanolcd-tangnano.fs blinky-tec0117.fs blinky-runber.fs \
pll-52-tangnano.fs pll-80-tangnano.fs \
pll-54-tangnano1k.fs pll-81-tangnano1k.fs \
pll-dyn-tangnano.fs pll-dyn-tangnano1k.fs \
pll-nanolcd-tangnano.fs pll-tangnano4k.fs
pll-tangnano4k.fs pll2-tangnano9k.fs \
pll-nanolcd-tangnano.fs pll-nanolcd-tangnano9k.fs

unpacked: attosoc-tec0117-unpacked.v nanolcd-tangnano-unpacked.v blinky-tec0117-unpacked.v blinky-runber-unpacked.v \
blinky-tangnano-unpacked.v blinky-honeycomb-unpacked.v shift-tec0117-unpacked.v shift-runber-unpacked.v \
Expand All @@ -38,7 +39,8 @@ unpacked: attosoc-tec0117-unpacked.v nanolcd-tangnano-unpacked.v blinky-tec0117-
pll-52-tangnano-unpacked.v pll-80-tangnano-unpacked.v \
pll-54-tangnano1k-unpacked.v pll-81-tangnano1k-unpacked.v \
pll-dyn-tangnano-unpacked.v pll-dyn-tangnano1k-unpacked.v \
pll-nanolcd-tangnano-unpacked.v pll-tangnano4k-unpacked.v
pll2-tangnano9k-unpacked.v \
pll-nanolcd-tangnano-unpacked.v pll-nanolcd-tangnano9k-unpacked.v pll-tangnano4k-unpacked.v

clean:
rm -f *.json *.fs *-unpacked.v
Expand Down Expand Up @@ -84,6 +86,9 @@ pll-tangnano4k.json: pll-tangnano4k-synth.json tangnano4k.cst
%-tangnano9k.fs: %-tangnano9k.json
gowin_pack -d GW1N-9C -o $@ $^

pll-nanolcd-tangnano9k.fs: pll-nanolcd-tangnano9k.json
gowin_pack -d GW1N-9C --sspi_as_gpio --mspi_as_gpio -o $@ $^

%-tangnano9k.json: %-tangnano9k-synth.json tangnano9k.cst
$(NEXTPNR) --json $< --write $@ --device GW1NR-LV9QN88PC6/I5 --family GW1N-9C --cst tangnano9k.cst

Expand All @@ -108,7 +113,13 @@ tonegen-tec0117-synth.json: tonegen/top.v tonegen/sddac.v tonegen/cordic.v
nanolcd-tangnano-synth.json: nanolcd/TOP.v nanolcd/VGAMod.v
$(YOSYS) -p "read_verilog $^; synth_gowin -json $@"

pll-nanolcd-tangnano-synth.json: pll-nanolcd/TOP.v pll-nanolcd/VGAMod.v
pll-nanolcd-tangnano-synth.json: pll/GW1N-1-dyn.vh pll-nanolcd/TOP.v pll-nanolcd/VGAMod.v
$(YOSYS) -p "read_verilog $^; synth_gowin -noalu -json $@"

pll-nanolcd-tangnano9k-synth.json: pll/GW1N-9C-dyn.vh pll-nanolcd/TOP.v pll-nanolcd/VGAMod.v
$(YOSYS) -p "read_verilog $^; synth_gowin -noalu -json $@"

pll2-tangnano9k-synth.json: pll/GW1N-9C-dyn.vh pll2.v pll/rpll.v
$(YOSYS) -p "read_verilog $^; synth_gowin -noalu -json $@"

%-tec0117-synth.json: %.v
Expand All @@ -117,10 +128,10 @@ pll-nanolcd-tangnano-synth.json: pll-nanolcd/TOP.v pll-nanolcd/VGAMod.v
%-runber-synth.json: %.v
$(YOSYS) -D LEDS_NR=8 -D OSC_TYPE_OSC -p "read_verilog $^; synth_gowin -json $@"

pll-%-tangnano-synth.json: pll/GW1N-1-%.vh pll.v
pll-%-tangnano-synth.json: pll/GW1N-1-%.vh pll.v pll/rpll.v
$(YOSYS) -D LEDS_NR=3 -p "read_verilog $^; synth_gowin -json $@"

pll-%-tangnano1k-synth.json: pll/GW1NZ-1-%.vh pll.v
pll-%-tangnano1k-synth.json: pll/GW1NZ-1-%.vh pll.v pll/rpll.v
$(YOSYS) -D LEDS_NR=3 -p "read_verilog $^; synth_gowin -json $@"

%-tangnano-synth.json: %.v
Expand All @@ -132,7 +143,7 @@ pll-%-tangnano1k-synth.json: pll/GW1NZ-1-%.vh pll.v
%-tangnano4k-synth.json: %.v
$(YOSYS) -D LEDS_NR=6 -D OSC_TYPE_OSCZ -p "read_verilog $^; synth_gowin -json $@"

pll-tangnano4k-synth.json: pll-tangnano4k.v pllvr.v
pll-tangnano4k-synth.json: pll-tangnano4k.v pll/pllvr.v
$(YOSYS) -D LEDS_NR=6 -D OSC_TYPE_OSCZ -p "read_verilog $^; synth_gowin -json $@"

%-tangnano9k-synth.json: %.v
Expand Down
13 changes: 6 additions & 7 deletions examples/pll-nanolcd/TOP.v
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ module TOP
rPLL pll(
.CLKOUT(CLK_SYS), // 90MHz
.CLKIN(clk),
.CLKOUTD(CLK_PIX),
.CLKOUTD(CLK_PIX), // 9MHz
.CLKFB(GND),
.FBDSEL({GND,GND,GND,GND,GND,GND}),
.IDSEL({GND,GND,GND,GND,GND,GND}),
.ODSEL({GND,GND,GND,GND,GND,GND}),
.DUTYDA({GND,GND,GND,GND}),
.PSDA({GND,GND,GND,GND}),
.FDLY({GND,GND,GND,GND}),
.FDLY({GND,GND,GND,GND})
);
defparam pll.DEVICE = "GW1N-1";
defparam pll.FCLKIN = "24";
defparam pll.FBDIV_SEL = 29;
defparam pll.IDIV_SEL = 7;
defparam pll.DEVICE = `PLL_DEVICE;
defparam pll.FCLKIN = `PLL_FCLKIN;
defparam pll.FBDIV_SEL = `PLL_FBDIV_SEL_LCD;
defparam pll.IDIV_SEL = `PLL_IDIV_SEL_LCD;
defparam pll.ODIV_SEL = 8; // 90MHz sys clock
defparam pll.CLKFB_SEL="internal";
defparam pll.CLKOUTD3_SRC="CLKOUT";
Expand All @@ -54,7 +54,6 @@ rPLL pll(
defparam pll.CLKOUT_BYPASS="false";
defparam pll.CLKOUT_DLY_STEP=0;
defparam pll.CLKOUT_FT_DIR=1'b1;
defparam pll.DEVICE="GW1N-1";
defparam pll.DUTYDA_SEL="1000";
defparam pll.DYN_DA_EN="false";
defparam pll.DYN_FBDIV_SEL="false";
Expand Down
Loading

0 comments on commit 6d9ca9c

Please sign in to comment.