Skip to content

Commit

Permalink
Himbaechel. Add initial BSRAM support (#202)
Browse files Browse the repository at this point in the history
* Himbaechel. Add initial BSRAM support

- added support for data generation for BSRAM initialization in a bit image;
- BSRAM Bels are created;
- pROM/pROMX9 primitives with widths of 1, 2, 4, 8, 9, 16, 18 bits are supported.
- start of unpacking - only attributes.

Signed-off-by: YRabbit <[email protected]>

* Himbaechel. Add BSRAM for all chips.

The following primitives are implemented for the GW1N-1, GW2A-18,
GW2AR-18C, GW1NSR-4C, GW1NR-9C, GW1NR-9 and GW1N-4 chips:

    * pROM     - read only memory - (bitwidth: 1, 2, 4, 8, 16, 32).
    * pROMX9   - read only memory - (bitwidth: 9, 18, 36).
    * SDPB     - semidual port    - (bitwidth: 1, 2, 4, 8, 16, 32).
    * SDPX9B   - semidual port    - (bitwidth: 9, 18, 36).
    * DPB      - dual port        - (bitwidth: 16).
    * DPX9B    - dual port        - (bitwidth: 18).
    * SP       - single port      - (bitwidth: 1, 2, 4, 8, 16, 32).
    * SPX9     - single port      - (bitwidth: 9, 18, 36).

For GW1NSR-4C and GW1NR-9 chips, SP/SPX9 primitives with data widths of
32/36 bits are implemented using a pair of 16-bit wide primitives.

Added examples for all boards except those based on GW1NSR-4C, GW1NR-9
and GW1N-4 chips for memory primitives with a width of 8 bits (as well
as 16 bits where 8 is not supported).

And these very examples are the weakest point - I tried to make the
primitives themselves work and the result of compiling the examples was
similar between the vendor IDE and Apicula, but the examples themselves
were poorly written. This is due to my lack of experience working with
BSRAM.  In general, they need to be rewritten.

Signed-off-by: YRabbit <[email protected]>

* Fix symbolic links.

Signed-off-by: YRabbit <[email protected]>

* Fix another symbolic link.

Signed-off-by: YRabbit <[email protected]>

---------

Signed-off-by: YRabbit <[email protected]>
  • Loading branch information
yrabbit authored Dec 3, 2023
1 parent a4f04e0 commit 2577559
Show file tree
Hide file tree
Showing 44 changed files with 3,121 additions and 23 deletions.
8 changes: 4 additions & 4 deletions apycula/attrids.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,16 +332,16 @@
'SPB_BEHB': 50,
'SPB_BELB': 51,
'SPA_MODE': 52,
'SPA_REG_MODE': 53,
'SPA_REGMODE': 53,
'SPB_MODE': 54,
'SPB_REG_MODE': 55,
'SPB_REGMODE': 55,
'ROMA_DATA_WIDTH': 56,
'ROMB_DATA_WIDTH': 57,
'ROM_DATA_WIDTH': 58,
'ROM_PORTA_BEHB': 59,
'ROM_PORTA_BELB': 60,
'ROM_PORTA_REGMODE':61,
'ROM_PORTB_REGMODE':62,
'ROMA_REGMODE': 61,
'ROMB_REGMODE': 62,
'PORTB_BELB': 63,
'PORTA_MODE': 64,
'PORTB_MODE': 65,
Expand Down
7 changes: 7 additions & 0 deletions apycula/bslib.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ def compressLine(line, key8Z, key4Z, key2Z):
newline += val.replace(2 * b'\x00', bytes([key2Z]))
return newline

def write_bitstream_with_bsram_init(fname, bs, hdr, ftr, compress, bsram_init):
new_bs = np.vstack((bs, bsram_init))
new_hdr = hdr.copy()
frames = int.from_bytes(new_hdr[-1][2:], 'big') + bsram_init.shape[0]
new_hdr[-1][2:] = frames.to_bytes(2, 'big')
write_bitstream(fname, new_bs, new_hdr, ftr, compress)

def write_bitstream(fname, bs, hdr, ftr, compress):
bs = np.fliplr(bs)
if compress:
Expand Down
134 changes: 126 additions & 8 deletions apycula/chipdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from collections import namedtuple
import numpy as np
import apycula.fuse_h4x as fuse
from apycula.wirenames import wirenames, clknames, clknumbers, hclknames, hclknumbers
from apycula.wirenames import wirenames, wirenumbers, clknames, clknumbers, hclknames, hclknumbers
from apycula import pindef

# the character that marks the I/O attributes that come from the nextpnr
Expand Down Expand Up @@ -447,6 +447,7 @@ def set_banks(fse, db):
13: 'BSRAM',
14: 'DSP',
15: 'PLL',
39: 'BSRAM_INIT',
59: 'CFG',
62: 'OSC',
63: 'USB',
Expand All @@ -464,6 +465,10 @@ def set_banks(fse, db):
26: 'CLS1',
27: 'CLS2',
28: 'CLS3',
29: 'BSRAM_DP',
30: 'BSRAM_SDP',
31: 'BSRAM_SP',
32: 'BSRAM_ROM',
35: 'PLL',
37: 'BANK',
40: 'IOBC',
Expand Down Expand Up @@ -1191,7 +1196,7 @@ def fse_iologic(device, fse, ttyp):
# the column indicated there is the last column of the left quadrant.

# It is enough to empirically determine the correspondence of clocks and
# speakers in the new quadrant (even three clocks is enough, since the fourth
# columns in the new quadrant (even three clocks is enough, since the fourth
# becomes obvious).
# [3, 2, 1, 0] turned out to be the unwritten standard for all the chips studied.

Expand Down Expand Up @@ -1228,9 +1233,9 @@ def fse_iologic(device, fse, ttyp):
# wires of the same name involved in some kind of switching anywhere in the
# chip are combined into one Himbaechel node. Further, when routing, there is
# already a choice of which pip to use and which cell.
# It also follows that for the Himbaechel watch wires should not be mixed
# It also follows that for the Himbaechel clock wires should not be mixed
# together with any other wires. At least I came to this conclusion and that
# is why the HCLK wires, which have the same numbers as the watch spines, are
# is why the HCLK wires, which have the same numbers as the clock spines, are
# stored separately.

# dat['CmuxIns'] and 80 - here, the places of entry points into the clock
Expand Down Expand Up @@ -1386,7 +1391,7 @@ def fse_create_simplio_rows(dev, dat):
dev.simplio_rows.add(row)

def fse_create_tile_types(dev, dat):
type_chars = 'PCMI'
type_chars = 'PCMIB'
for fn in type_chars:
dev.tile_types[fn] = set()
for row, rd in enumerate(dat['grid']):
Expand All @@ -1404,6 +1409,25 @@ def fse_create_tile_types(dev, dat):
j -= 1
dev.tile_types[fn].add(dev.grid[i][j].ttyp)

def get_tile_types_by_func(dev, dat, fse, fn):
ttypes = set()
fse_grid = fse['header']['grid'][61]
for row, rd in enumerate(dat['grid']):
for col, type_char in enumerate(rd):
if type_char == fn:
i = row
if i > 0:
i -= 1
if i == len(fse_grid):
i -= 1
j = col
if j > 0:
j -= 1
if j == len(fse_grid[0]):
j -= 1
ttypes.add(fse_grid[i][j])
return ttypes

def fse_create_diff_types(dev, device):
dev.diff_io_types = ['ELVDS_IBUF', 'ELVDS_OBUF', 'ELVDS_IOBUF', 'ELVDS_TBUF',
'TLVDS_IBUF', 'TLVDS_OBUF', 'TLVDS_IOBUF', 'TLVDS_TBUF']
Expand Down Expand Up @@ -1530,6 +1554,15 @@ def fse_create_gsr(dev, device):
dev.extra_func.setdefault((row, col), {}).update(
{'gsr': {'wire': 'C4'}})

def fse_bram(fse, aux = False):
bels = {}
name = 'BSRAM'
if aux:
name = 'BSRAM_AUX'
bels[name] = Bel()
return bels


def disable_plls(dev, device):
if device in {'GW2A-18C'}:
# (9, 0) and (9, 55) are the coordinates of cells when trying to place
Expand All @@ -1547,6 +1580,8 @@ def from_fse(device, fse, dat):
fse_create_simplio_rows(dev, dat)
ttypes = {t for row in fse['header']['grid'][61] for t in row}
tiles = {}
bram_ttypes = get_tile_types_by_func(dev, dat, fse, 'B')
bram_aux_ttypes = get_tile_types_by_func(dev, dat, fse, 'b')
for ttyp in ttypes:
w = fse[ttyp]['width']
h = fse[ttyp]['height']
Expand All @@ -1559,13 +1594,17 @@ def from_fse(device, fse, dat):
tile.alonenode_6 = fse_alonenode(fse, ttyp, 6)
if 5 in fse[ttyp]['shortval']:
tile.bels = fse_luts(fse, ttyp)
if 51 in fse[ttyp]['shortval']:
elif 51 in fse[ttyp]['shortval']:
tile.bels = fse_osc(device, fse, ttyp)
elif ttyp in bram_ttypes:
tile.bels = fse_bram(fse)
elif ttyp in bram_aux_ttypes:
tile.bels = fse_bram(fse, True)
# These are the cell types in which PLLs can be located. To determine,
# we first take the coordinates of the cells with the letters P and p
# from the dat['grid'] table, and then, using these coordinates,
# determine the type from fse['header']['grid'][61][row][col]
if ttyp in [42, 45, 74, 75, 76, 77, 78, 79, 86, 87, 88, 89]:
elif ttyp in [42, 45, 74, 75, 76, 77, 78, 79, 86, 87, 88, 89]:
tile.bels = fse_pll(device, fse, ttyp)
tile.bels.update(fse_iologic(device, fse, ttyp))
tiles[ttyp] = tile
Expand Down Expand Up @@ -1775,6 +1814,7 @@ def json_pinout(device):
_ides16_fixed_outputs = { 'Q0': 'F2', 'Q1': 'F3', 'Q2': 'F4', 'Q3': 'F5', 'Q4': 'Q0',
'Q5': 'Q1', 'Q6': 'Q2', 'Q7': 'Q3', 'Q8': 'Q4', 'Q9': 'Q5', 'Q10': 'F0',
'Q11': 'F1', 'Q12': 'F2', 'Q13': 'F3', 'Q14': 'F4', 'Q15': 'F5'}
_bsram_control_ins = ['CLK', 'OCE', 'CE', 'RESET', 'WRE']
def get_pllout_global_name(row, col, wire, device):
for name, loc in _pll_loc[device].items():
if loc == (row, col, wire):
Expand All @@ -1787,7 +1827,7 @@ def dat_portmap(dat, dev, device):
for name, bel in tile.bels.items():
if bel.portmap:
# GW2A has same PLL in different rows
if not (name.startswith("RPLLA") and device in {'GW2A-18', 'GW2A-18C'}):
if (not (name.startswith("RPLLA") and device in {'GW2A-18', 'GW2A-18C'})) and name != "BSRAM":
continue
if name.startswith("IOB"):
if row in dev.simplio_rows:
Expand Down Expand Up @@ -1845,6 +1885,84 @@ def dat_portmap(dat, dev, device):
# dummy Input, we'll make a special pips for it
bel.portmap[nam] = "FCLK"
bel.portmap.update(_ides16_fixed_outputs)
elif name == 'BSRAM':
# dat['BsramOutDlt'] and dat['BsramOutDlt'] indicate port offset in cells
wire2node = {} # some wires used for >1 port, remember node
for i in range(len(dat['BsramOut'])):
off = dat['BsramOutDlt'][i]
wire_idx = dat['BsramOut'][i]
if wire_idx < 0:
continue
wire = wirenames[wire_idx]
# outs sequence: DO0-35, DOA0-17, DOB0-17
if i < 36:
nam = f'DO{i}'
elif i < 54:
nam = f'DOA{i - 36}'
else:
nam = f'DOB{i - 36 - 18}'
# for aux cells create Himbaechel nodes
if off:
bel.portmap[nam] = f'BSRAM{nam}{wire}'
node = wire2node.get((row, col + off, wire), None)
if node:
dev.nodes[node][1].add((row, col, f'BSRAM{nam}{wire}'))
else:
dev.nodes.setdefault(f'X{col}Y{row}/BSRAM{nam}{wire}', ("BSRAM_O", {(row, col, f'BSRAM{nam}{wire}')}))[1].add((row, col + off, wire))
wire2node[(row, col + off, wire)] = f'X{col}Y{row}/BSRAM{nam}{wire}'
else:
bel.portmap[nam] = wire
for i in range(len(dat['BsramIn']) + 6):
if i < 132:
off = dat['BsramInDlt'][i]
wire_idx = dat['BsramIn'][i]
if wire_idx < 0:
continue
elif i in range(132, 135):
nam = f'BLKSELA{i - 132}'
wire_idx = dat['BsramIn'][i - 132 + 15]
off = [0, 0, 2][i - 132]
else:
nam = f'BLKSELB{i - 135}'
wire_idx = wirenumbers[['CE2', 'LSR2', 'CE1'][i - 135]]
off = [1, 1, 2][i - 135]
wire = wirenames[wire_idx]
# helping the clock router
wire_type = 'BSRAM_I'
if wire.startswith('CLK') or wire.startswith('CE') or wire.startswith('LSR'):
wire_type = 'TILE_CLK'
# ins sequence: control(0-17), ADA0-13, AD0-13, DIA0-17,
# DI0-35, ADB0-13, DIB0-17, control(133-138)
# controls - A, B, '' like all controls for A (CLKA,), then for B (CLKB),
# then without modifier '' (CLK)
if i < 18:
if i < 15:
nam = _bsram_control_ins[i % 5] + ['A', 'B', ''][i // 5]
else:
nam = f'BLKSEL{i - 15}'
elif i < 32:
nam = f'ADA{i - 18}'
elif i < 46:
nam = f'AD{i - 32}'
elif i < 64:
nam = f'DIA{i - 46}'
elif i < 100:
nam = f'DI{i - 64}'
elif i < 114:
nam = f'ADB{i - 100}'
elif i < 132:
nam = f'DIB{i - 114}'
# for aux cells create Himbaechel nodes
if off:
bel.portmap[nam] = f'BSRAM{nam}{wire}'
node = wire2node.get((row, col + off, wire), None)
if node:
dev.nodes[node][1].add((row, col, f'BSRAM{nam}{wire}'))
else:
dev.nodes.setdefault(f'X{col}Y{row}/BSRAM{nam}{wire}', (wire_type, {(row, col, f'BSRAM{nam}{wire}')}))[1].add((row, col + off, wire))
wire2node[(row, col + off, wire)] = f'X{col}Y{row}/BSRAM{nam}{wire}'
else:
bel.portmap[nam] = wire
elif name == 'RPLLA':
# The PllInDlt table seems to indicate in which cell the
# inputs are actually located.
Expand Down
Loading

0 comments on commit 2577559

Please sign in to comment.