Skip to content
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

Add dynamic clock control #260

Merged
merged 5 commits into from
Aug 12, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/chipdb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ jobs:
strategy:
fail-fast: false
matrix:
yosys: [main, yosys-0.39]
yosys: [main, yosys-0.44]
nextpnr: [master, nextpnr-0.7]
steps:
- uses: actions/checkout@v4
Expand Down
32 changes: 32 additions & 0 deletions apycula/attrids.py
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,38 @@
'SET': 16,
'RESET': 17
}

# DCS
# just quadrant index
dcs_attrids = {
'1': 1,
'2': 0,
'3': 2,
'4': 3,
}
# There are no combinations here since the DCS primitive has only one
# parameter, so by specifying the value of this parameter and generating the
# image it is easy to find the number in this table.
dcs_attrvals = {
'UNKNOWN': 0,
'FALLING': 1,
'RISING': 2,
'CLK0': 3,
'CLK1': 4,
'CLK2': 5,
'CLK3': 6,
'CLK0_VCC': 13,
'CLK1_VCC': 14,
'CLK2_GND': 15,
'CLK2_VCC': 16,
'CLK3_VCC': 17,
'CLK0_GND': 18,
'CLK1_GND': 19,
'CLK3_GND': 20,
'GND': 21,
'VCC': 22,
}

# DLL
dll_attrids = {
'CLKSEL': 0,
Expand Down
108 changes: 103 additions & 5 deletions apycula/chipdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class Device:
# allowable values of bel attributes
# {table_name: [(attr_id, attr_value)]}
logicinfo: Dict[str, List[Tuple[int, int]]] = field(default_factory=dict)
# fuses for single feature only
# {ttype: {table_name: {feature: {bits}}}
longfuses: Dict[int, Dict[str, Dict[Tuple[int,], Set[Coord]]]] = 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)
Expand Down Expand Up @@ -464,6 +467,8 @@ def set_banks(fse, db):
_known_tables = {
4: 'CONST',
5: 'LUT',
18: 'DCS6',
19: 'DCS7',
20: 'GSR',
21: 'IOLOGICA',
22: 'IOLOGICB',
Expand Down Expand Up @@ -513,6 +518,15 @@ def fse_fill_logic_tables(dev, fse):
# shortval
ttypes = {t for row in fse['header']['grid'][61] for t in row}
for ttyp in ttypes:
if 'longfuse' in fse[ttyp].keys():
ttyp_rec = dev.longfuses.setdefault(ttyp, {})
for lftable in fse[ttyp]['longfuse'].keys():
if lftable in _known_tables:
table = ttyp_rec.setdefault(_known_tables[lftable], {})
else:
table = ttyp_rec.setdefault(f"unknown_{lftable}", {})
for f, *fuses in fse[ttyp]['longfuse'][lftable]:
table[(f, )] = {fuse.fuse_lookup(fse, ttyp, f) for f in unpad(fuses)}
if 'shortval' in fse[ttyp].keys():
ttyp_rec = dev.shortval.setdefault(ttyp, {})
for stable in fse[ttyp]['shortval'].keys():
Expand Down Expand Up @@ -1318,13 +1332,14 @@ def fse_create_clocks(dev, device, dat: Datfile, fse):


spines = {f'SPINE{i}' for i in range(32)}
dcs_inputs = {f'P{i}{j}{k}' for i in range(1, 5) for j in range(6, 8) for k in "ABCD"}
for row, rd in enumerate(dev.grid):
for col, rc in enumerate(rd):
for dest, srcs in rc.pure_clock_pips.items():
for src in srcs.keys():
if src in spines and not dest.startswith('GT'):
add_node(dev, src, "GLOBAL_CLK", row, col, src)
if dest in spines:
if dest in spines or dest in dcs_inputs:
add_node(dev, dest, "GLOBAL_CLK", row, col, dest)
for src in { wire for wire in srcs.keys() if wire not in {'VCC', 'VSS'}}:
add_node(dev, src, "GLOBAL_CLK", row, col, src)
Expand Down Expand Up @@ -1370,13 +1385,91 @@ def fse_create_clocks(dev, device, dat: Datfile, fse):
if col == tap_col:
spine = quad * 8 + spine_pair
dev.nodes.setdefault(f'SPINE{spine}', ("GLOBAL_CLK", set()))[1].add((row, col, f'SPINE{spine}'))
# XXX skip clock 6 and 7 for now
if spine_pair not in {2, 3}:
dev.nodes.setdefault(f'SPINE{spine + 4}', ("GLOBAL_CLK", set()))[1].add((row, col, f'SPINE{spine + 4}'))
dev.nodes.setdefault(f'SPINE{spine + 4}', ("GLOBAL_CLK", set()))[1].add((row, col, f'SPINE{spine + 4}'))
else:
dev.nodes.setdefault(node0_name, ("GLOBAL_CLK", set()))[1].add((row, col, 'GT00'))
dev.nodes.setdefault(node1_name, ("GLOBAL_CLK", set()))[1].add((row, col, 'GT10'))

# According to the Gowin Clock User Guide, the DQCE primitives are located
# between the "spine" wires (in our terminology) and the central MUX, which
# selects the clock source for that spine. We detect cells with DQCE by
# instantiating this primitive and connecting the CE input to the button -
# in the images generated by the Gowin IDE, it is easy to trace the wires
# from the button to the cell and pin being used.
# It was found that the CE pin depends only on the "spine" number and does
# not depend on the quadrant or chip. The cells used also do not depend on
# the chip, but only on the cell type: here is the correspondence of the
# types to the quadrants for which the corresponding DQCEs are responsible:
# |
# quadrant 2 type 80 | type 85 quadrant 1
# ------------------------+--------------------------
# quadrant 3 type 81 | type 84 quadrant 4
# |

for q, ttyp in enumerate([85, 80, 81, 84]):
# stop if chip has only 2 quadrants
if q < 2 and device not in {'GW1N-9', 'GW1N-9C', 'GW2A-18', 'GW2A-18C'}:
continue
for row in range(dev.rows):
for col in range(dev.cols):
if ttyp == fse['header']['grid'][61][row][col]:
break
else:
continue
break
extra_func = dev.extra_func.setdefault((row, col), {})
dqce_block = extra_func.setdefault('dqce', {})
for j in range(6):
dqce = dqce_block.setdefault(j, {})
dqce[f'clkin'] = f'SPINE{q * 8 + j}'
dqce[f'ce'] = ['A0', 'B0', 'C0', 'D0', 'A1', 'B1'][j]

# As it turned out, the DCS are located in the same cells, but their
# relationship with the quadrants is different.
# By generating images where the button was connected to the clock
# selection inputs (CLK0-3) as well as to the SELFORCE input, it was
# possible to determine the correspondence of the wires in these cells.
# |
# quadrant 2, spine14 dcs type 80 | quadrant 1, spine 6 dcs type 85
# spine15 dcs type 81 | spine 7 dcs type 84
# -------------------------------------------------------------------
# quadrant 3, spine22 dcs type 80 | quadrant 4, spine 30 dcs type 85
# spine23 dcs type 81 | spine 31 dcs type 84
# |
# At the moment we will organize the description of DCS as:
# 'dcs':
# 0 /* first DCS */ : its ports
# 1 /* second DCS*/ : its ports
for q, types in enumerate([(85, 84), (80, 81), (80, 81), (85, 84)]):
# stop if chip has only 2 quadrants
if q < 2 and device not in {'GW1N-9', 'GW1N-9C', 'GW2A-18', 'GW2A-18C'}:
continue
for j in range(2):
for row in range(dev.rows):
for col in range(dev.cols):
if types[j] == fse['header']['grid'][61][row][col]:
break
else:
continue
break
extra_func = dev.extra_func.setdefault((row, col), {})
dcs_block = extra_func.setdefault('dcs', {})
dcs = dcs_block.setdefault(q // 2, {})
spine_idx = f'SPINE{q * 8 + j + 6}'
dcs['clkout'] = spine_idx
dev.nodes.setdefault(spine_idx, ("GLOBAL_CLK", set()))[1].add((row, col, spine_idx))
dcs['clk'] = []
for port in "ABCD":
wire_name = f'P{q + 1}{j + 6}{port}'
dcs['clk'].append(wire_name)
dev.nodes.setdefault(wire_name, ("GLOBAL_CLK", set()))[1].add((row, col, wire_name))
if q < 2:
dcs['selforce'] = 'C2'
dcs['clksel'] = ['C1', 'D1', 'A2', 'B2']
else:
dcs[f'selforce'] = 'D3'
dcs['clksel'] = ['D2', 'A3', 'B3', 'C3']

# These features of IO on the underside of the chip were revealed during
# operation. The first (normal) mode was found in a report by @LoneTech on
# 4/1/2022, when it turned out that the pins on the bottom edge of the GW1NR-9
Expand Down Expand Up @@ -1734,7 +1827,7 @@ def from_fse(device, fse, dat: Datfile):
def get_table_fuses(attrs, table):
bits = set()
for key, fuses in table.items():
# all 2/16 "features" must be present to be able to use a set of bits from the record
# all 1/2/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"
Expand All @@ -1755,6 +1848,11 @@ def get_table_fuses(attrs, table):
bits.update(fuses)
return bits

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

# get fuses for attr/val set using shortval table for ttyp
# returns a bit set
def get_shortval_fuses(dev, ttyp, attrs, table_name):
Expand Down
54 changes: 51 additions & 3 deletions apycula/gowin_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from contextlib import closing
from apycula import codegen
from apycula import chipdb
from apycula.chipdb import add_attr_val, get_shortval_fuses, get_longval_fuses, get_bank_fuses
from apycula.chipdb import add_attr_val, get_shortval_fuses, get_longval_fuses, get_bank_fuses, get_long_fuses
from apycula import attrids
from apycula import bslib
from apycula import bitmatrix
Expand Down Expand Up @@ -185,7 +185,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|BSRAM|ALU|MULTALU18X18|MULTALU36X18|MULTADDALU18X18|MULT36X36|MULT18X18|MULT9X9|PADD18|PADD9|BANDGAP)(\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|BSRAM|ALU|MULTALU18X18|MULTALU36X18|MULTADDALU18X18|MULT36X36|MULT18X18|MULT9X9|PADD18|PADD9|BANDGAP|DQCE|DCS)(\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 @@ -376,7 +376,6 @@ def add_pll_default_attrs(attrs):
pll_inattrs[k] = v
return pll_inattrs


# typ - PLL type (RPLL, etc)
def set_pll_attrs(db, typ, idx, attrs):
pll_inattrs = add_pll_default_attrs(attrs)
Expand Down Expand Up @@ -503,6 +502,28 @@ def set_pll_attrs(db, typ, idx, attrs):
add_attr_val(db, 'PLL', fin_attrs, attrids.pll_attrids[attr], val)
return fin_attrs

_dcs_spine2quadrant_idx = {
'SPINE6' : ('1', 'DCS6'),
'SPINE7' : ('1', 'DCS7'),
'SPINE14' : ('2', 'DCS6'),
'SPINE15' : ('2', 'DCS7'),
'SPINE22' : ('3', 'DCS6'),
'SPINE23' : ('3', 'DCS7'),
'SPINE30' : ('4', 'DCS6'),
'SPINE31' : ('4', 'DCS7'),
}
def set_dcs_attrs(db, spine, attrs):
q, _ = _dcs_spine2quadrant_idx[spine]
dcs_attrs = {}
dcs_attrs[q] = attrs['DCS_MODE']

fin_attrs = set()
for attr, val in dcs_attrs.items():
if isinstance(val, str):
val = attrids.dcs_attrvals[val]
add_attr_val(db, 'DCS', fin_attrs, attrids.dcs_attrids[attr], val)
return fin_attrs

_bsram_bit_widths = { 1: '1', 2: '2', 4: '4', 8: '9', 9: '9', 16: '16', 18: '16', 32: 'X36', 36: 'X36'}
def set_bsram_attrs(db, typ, params):
bsram_attrs = {}
Expand Down Expand Up @@ -2431,6 +2452,33 @@ def place(db, tilemap, bels, cst, args):
cfg_tile = tilemap[(0, 37)]
for r, c in bits:
cfg_tile[r][c] = 1
elif typ == 'DQCE':
# Himbaechel only
pipre = re.compile(r"X(\d+)Y(\d+)/([\w_]+)/([\w_]+)")
if 'DQCE_PIP' not in attrs:
continue
pip = attrs['DQCE_PIP']
res = pipre.fullmatch(pip)
if not res:
raise Exception(f"Bad DQCE pip {pip} at {cellname}")
pip_col, pip_row, dest, src = res.groups()
pip_row = int(pip_row)
pip_col = int(pip_col)

pip_tiledata = db.grid[pip_row][pip_col]
pip_tile = tilemap[(pip_row, pip_col)]
bits = pip_tiledata.clock_pips[dest][src]
for r, c in bits:
pip_tile[r][c] = 1
elif typ == 'DCS':
if 'DCS_MODE' not in attrs:
continue
spine = db.extra_func[row - 1, col - 1]['dcs'][int(num)]['clkout']
dcs_attrs = set_dcs_attrs(db, spine, attrs)
_, idx = _dcs_spine2quadrant_idx[spine]
bits = get_long_fuses(db, tiledata.ttyp, dcs_attrs, idx)
for r, c in bits:
tile[r][c] = 1
else:
print("unknown type", typ)

Expand Down
5 changes: 5 additions & 0 deletions examples/himbaechel/Makefile.himbaechel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ all: \
dsp-mult36x36-tangnano20k.fs dsp-padd9-tangnano20k.fs dsp-padd18-tangnano20k.fs \
dsp-mult9x9-tangnano20k.fs dsp-alu54d-tangnano20k.fs dsp-multalu18x18-tangnano20k.fs \
dsp-multalu36x18-tangnano20k.fs dsp-multaddalu18x18-tangnano20k.fs \
dqce-tangnano20k.fs dcs-tangnano20k.fs \
\
blinky-primer20k.fs shift-primer20k.fs blinky-tbuf-primer20k.fs blinky-oddr-primer20k.fs \
blinky-osc-primer20k.fs tlvds-primer20k.fs elvds-primer20k.fs oddr-tlvds-primer20k.fs \
Expand All @@ -29,6 +30,7 @@ all: \
dsp-mult36x36-primer20k.fs dsp-padd9-primer20k.fs dsp-padd18-primer20k.fs \
dsp-mult9x9-primer20k.fs dsp-alu54d-primer20k.fs dsp-multalu18x18-primer20k.fs \
dsp-multalu36x18-primer20k.fs dsp-multaddalu18x18-primer20k.fs \
dqce-primer20k.fs dcs-primer20k.fs \
\
blinky-tangnano.fs shift-tangnano.fs blinky-tbuf-tangnano.fs blinky-oddr-tangnano.fs \
blinky-osc-tangnano.fs elvds-tangnano.fs oddr-elvds-tangnano.fs pll-nanolcd-tangnano.fs \
Expand All @@ -45,6 +47,7 @@ all: \
bsram-pROM-tangnano1k.fs bsram-SDPB-tangnano1k.fs bsram-DPB16-tangnano1k.fs \
bsram-SP-tangnano1k.fs bsram-pROMX9-tangnano1k.fs bsram-SDPX9B-tangnano1k.fs \
bsram-SPX9-tangnano1k.fs bsram-DPX9B18-tangnano1k.fs \
dqce-tangnano1k.fs dcs-tangnano1k.fs \
\
blinky-tangnano4k.fs shift-tangnano4k.fs blinky-tbuf-tangnano4k.fs blinky-oddr-tangnano4k.fs \
blinky-osc-tangnano4k.fs tlvds-tangnano4k.fs elvds-tangnano4k.fs oddr-tlvds-tangnano4k.fs \
Expand All @@ -57,6 +60,7 @@ all: \
dsp-mult36x36-tangnano4k.fs dsp-padd9-tangnano4k.fs dsp-padd18-tangnano4k.fs \
dsp-mult9x9-tangnano4k.fs dsp-alu54d-tangnano4k.fs dsp-multalu18x18-tangnano4k.fs \
dsp-multalu36x18-tangnano4k.fs dsp-multaddalu18x18-tangnano4k.fs \
dqce-tangnano4k.fs dcs-tangnano4k.fs \
\
blinky-tangnano9k.fs shift-tangnano9k.fs blinky-tbuf-tangnano9k.fs blinky-oddr-tangnano9k.fs \
blinky-osc-tangnano9k.fs tlvds-tangnano9k.fs elvds-tangnano9k.fs oddr-tlvds-tangnano9k.fs \
Expand All @@ -71,6 +75,7 @@ all: \
dsp-mult36x36-tangnano9k.fs dsp-padd9-tangnano9k.fs dsp-padd18-tangnano9k.fs \
dsp-mult9x9-tangnano9k.fs dsp-alu54d-tangnano9k.fs dsp-multalu18x18-tangnano9k.fs \
dsp-multalu36x18-tangnano9k.fs dsp-multaddalu18x18-tangnano9k.fs \
dqce-tangnano9k.fs dcs-tangnano9k.fs \
\
blinky-szfpga.fs shift-szfpga.fs blinky-tbuf-szfpga.fs blinky-oddr-szfpga.fs \
blinky-osc-szfpga.fs tlvds-szfpga.fs elvds-szfpga.fs oddr-tlvds-szfpga.fs \
Expand Down
35 changes: 35 additions & 0 deletions examples/himbaechel/dcs.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* Pressing the button stops the blinking because no clock source will be selected */
module top (
input clk,
input key_i,
input rst_i,
output [`LEDS_NR-1:0] led
);

reg [31:0] counter;
reg [31:0] counter2;
wire clk1, clk2;

wire key = key_i ^ `INV_BTN;

DCS dcs(
.CLK0(1'b1),
.CLK1(clk),
.CLK2(1'b1),
.CLK3(1'b1),
.CLKSEL({1'b0, 1'b0, key, 1'b0}),
.SELFORCE(1'b1),
.CLKOUT(clk1)
);
defparam dcs.DCS_MODE="CLK1_GND";

always @(posedge clk1) begin
if (counter < 31'd1350_0000)
counter <= counter + 1;
else begin
counter <= 31'd0;
led[1:0] <= {~led[0],led[1]};
end
end

endmodule
Loading
Loading