Skip to content

Commit

Permalink
Merge pull request #260 from yrabbit/dyn-clk
Browse files Browse the repository at this point in the history
Add dynamic clock control
  • Loading branch information
yrabbit authored Aug 12, 2024
2 parents 6bee673 + 90c4a1d commit db76863
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 9 deletions.
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

0 comments on commit db76863

Please sign in to comment.