diff --git a/.github/workflows/chipdb.yml b/.github/workflows/chipdb.yml index 2ac59e5..e526c48 100644 --- a/.github/workflows/chipdb.yml +++ b/.github/workflows/chipdb.yml @@ -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 diff --git a/apycula/attrids.py b/apycula/attrids.py index bb5d45f..7568af7 100644 --- a/apycula/attrids.py +++ b/apycula/attrids.py @@ -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, diff --git a/apycula/chipdb.py b/apycula/chipdb.py index 2807f09..703647d 100644 --- a/apycula/chipdb.py +++ b/apycula/chipdb.py @@ -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) @@ -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', @@ -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(): @@ -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) @@ -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 @@ -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" @@ -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): diff --git a/apycula/gowin_pack.py b/apycula/gowin_pack.py index d1a40c8..257da33 100644 --- a/apycula/gowin_pack.py +++ b/apycula/gowin_pack.py @@ -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 @@ -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*)") @@ -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) @@ -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 = {} @@ -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) diff --git a/examples/himbaechel/Makefile.himbaechel b/examples/himbaechel/Makefile.himbaechel index 1e3de94..acb3ea7 100644 --- a/examples/himbaechel/Makefile.himbaechel +++ b/examples/himbaechel/Makefile.himbaechel @@ -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 \ @@ -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 \ @@ -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 \ @@ -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 \ @@ -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 \ diff --git a/examples/himbaechel/dcs.v b/examples/himbaechel/dcs.v new file mode 100644 index 0000000..62c3658 --- /dev/null +++ b/examples/himbaechel/dcs.v @@ -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 diff --git a/examples/himbaechel/dqce.v b/examples/himbaechel/dqce.v new file mode 100644 index 0000000..e5ec0cd --- /dev/null +++ b/examples/himbaechel/dqce.v @@ -0,0 +1,46 @@ +/* Pressing the button stops the blinking of one or another group of LEDs */ +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; +wire key1 = rst_i ^ `INV_BTN; + +DQCE dqce( + .CLKIN(clk), + .CE(key), + .CLKOUT(clk1) +); + +DQCE dqce2( + .CLKIN(clk), + .CE(key1), + .CLKOUT(clk2) +); + +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 + +always @(posedge clk2) begin + if (counter2 < 31'd1350_0000) + counter2 <= counter2 + 1; + else begin + counter2 <= 31'd0; + led[2] <= ~led[2]; + end +end + +endmodule