Skip to content

Commit

Permalink
Merge pull request YosysHQ#130 from cr1901/refactor-util-tools
Browse files Browse the repository at this point in the history
Refactor util tools
  • Loading branch information
gatecat committed Mar 13, 2020
2 parents 0363d4a + 8a266b9 commit 7283c5f
Show file tree
Hide file tree
Showing 14 changed files with 353 additions and 94 deletions.
14 changes: 14 additions & 0 deletions libtrellis/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,22 @@ install_manifest.txt
ecpbram
ecppack
ecpunpack
ecpmulti
ecppll
libtrellis.dylib
*~
ecpmulti
generated/

# Windows
ecpbram.exe
ecppack.exe
ecpunpack.exe
ecpmulti.exe
ecppll.exe
pytrellis.pyd

# Ninja
.ninja_*
build.ninja
rules.ninja
110 changes: 90 additions & 20 deletions libtrellis/src/Bitstream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,45 @@ static const vector<pair<std::string, uint8_t>> spi_modes =
static const uint32_t multiboot_flag = 1 << 20;
static const uint32_t background_flag = 0x2E000000;

// Bitstream generation can be tweaked in various ways; this class encapsulates
// these options and provides defaults that mimic Diamond-generated output.
class BitstreamOptions {
public:
BitstreamOptions(const Chip &chip) {
if (chip.info.family == "MachXO2") {
// Write frames out in order 0 => max or reverse (max => 0).
// This apparently does NOT apply to compressed bitstreams, which
// uses reversed_frames = true unconditionally.
reversed_frames = false;
dummy_bytes_after_preamble = 2;
crc_meta = 0xE0; // CRC check (0x80), once at end (0x40), dummy bits
// in bitstream, no dummy bytes after each frame.
// FIXME: Diamond seems to set include_dummy_bits for MachXO2
// sometimes (0x20). Add this functionality later, as I'm not sure
// any MachXO2 members have dummy bits at this time.
crc_after_each_frame = false;
dummy_bytes_after_frame = 0;
security_sed_space = 8;
} else if (chip.info.family == "ECP5") {
reversed_frames = true;
dummy_bytes_after_preamble = 4;
crc_meta = 0x91; // CRC check (0x80), per frame (bit 6 cleared),
// and there 1 dummy bytes after each frame (0x10).
crc_after_each_frame = true;
dummy_bytes_after_frame = 1;
security_sed_space = 12;
} else
throw runtime_error("Unknown chip family: " + chip.info.family);
};

bool reversed_frames;
size_t dummy_bytes_after_preamble;
uint8_t crc_meta;
bool crc_after_each_frame;
size_t dummy_bytes_after_frame;
size_t security_sed_space;
};

// The BitstreamReadWriter class stores state (including CRC16) whilst reading
// the bitstream
class BitstreamReadWriter {
Expand Down Expand Up @@ -199,7 +238,7 @@ class BitstreamReadWriter {
}
}

void write_compressed_frames(const std::vector<std::vector<uint8_t>> &frames_in) {
void write_compressed_frames(const std::vector<std::vector<uint8_t>> &frames_in, BitstreamOptions &ops) {
// Build a histogram of bytes to aid creating the dictionary
int histogram[256];
for (int i = 0; i < 256; i++)
Expand All @@ -223,7 +262,7 @@ class BitstreamReadWriter {
write_byte(dict_entries[i]);
// Write data
write_byte(uint8_t(BitstreamCommand::LSC_PROG_INCR_CMP));
write_byte(0x91); //CRC check, 1 dummy byte
write_byte(ops.crc_meta); //CRC check, 1 dummy byte
uint16_t frames = uint16_t(frames_in.size());
write_byte(uint8_t((frames >> 8) & 0xFF));
write_byte(uint8_t(frames & 0xFF));
Expand Down Expand Up @@ -286,8 +325,13 @@ class BitstreamReadWriter {
// This ensures compressed frame is 8-bit aligned
flush_bits();
// Post-frame CRC and 0xFF byte
insert_crc16();
write_byte(0xFF);
if(ops.crc_after_each_frame) {
insert_crc16();
}

for(size_t j = 0; j < ops.dummy_bytes_after_frame; j++) {
write_byte(0xFF);
}
}
}

Expand Down Expand Up @@ -530,13 +574,8 @@ Chip Bitstream::deserialise_chip(boost::optional<uint32_t> idcode) {
// This is the main bitstream payload
if (!chip)
throw BitstreamParseError("start of bitstream data before chip was identified", rd.get_offset());
bool reversed_frames;
if (chip->info.family == "MachXO2")
reversed_frames = false;
else if (chip->info.family == "ECP5")
reversed_frames = true;
else
throw BitstreamParseError("Unknown chip family: " + chip->info.family);

BitstreamOptions ops(chip.get()); // Only reversed_frames is meaningful here.

uint8_t params[3];
rd.get_bytes(params, 3);
Expand All @@ -561,7 +600,9 @@ Chip Bitstream::deserialise_chip(boost::optional<uint32_t> idcode) {
bytes_per_frame += (7 - ((bytes_per_frame - 1) % 8));
unique_ptr<uint8_t[]> frame_bytes = make_unique<uint8_t[]>(bytes_per_frame);
for (size_t i = 0; i < frame_count; i++) {
size_t idx = reversed_frames? (chip->info.num_frames - 1) - i : i;
// Apparently when a bitstream is compressed, even on
// MachXO2, frames are written in reverse order!
const size_t idx = (chip->info.num_frames - 1) - i;
if (cmd == BitstreamCommand::LSC_PROG_INCR_CMP)
rd.get_compressed_bytes(frame_bytes.get(), bytes_per_frame, compression_dict.get());
else
Expand Down Expand Up @@ -687,10 +728,13 @@ Bitstream Bitstream::serialise_chip_py(const Chip &chip) {

Bitstream Bitstream::serialise_chip(const Chip &chip, const map<string, string> options) {
BitstreamReadWriter wr;

BitstreamOptions ops(chip);

// Preamble
wr.write_bytes(preamble.begin(), preamble.size());
// Padding
wr.insert_dummy(4);
wr.insert_dummy(ops.dummy_bytes_after_preamble);

if (options.count("spimode")) {
auto spimode = find_if(spi_modes.begin(), spi_modes.end(), [&](const pair<string, uint8_t> &fp){
Expand Down Expand Up @@ -737,9 +781,16 @@ Bitstream Bitstream::serialise_chip(const Chip &chip, const map<string, string>
ctrl0 &= ~background_flag;
}
wr.write_uint32(ctrl0);

// Seems pretty consistent...
if (chip.info.family == "MachXO2") {
wr.insert_dummy(4);
}

// Init address
wr.write_byte(uint8_t(BitstreamCommand::LSC_INIT_ADDRESS));
wr.insert_zeros(3);

if (options.count("compress") && options.at("compress") == "yes") {
// First create an uncompressed array of frames
std::vector<std::vector<uint8_t>> frames_data;
Expand All @@ -758,33 +809,44 @@ Bitstream Bitstream::serialise_chip(const Chip &chip, const map<string, string>
}
}
// Then compress and write
wr.write_compressed_frames(frames_data);
wr.write_compressed_frames(frames_data, ops);
} else {
// Bitstream data
wr.write_byte(uint8_t(BitstreamCommand::LSC_PROG_INCR_RTI));
wr.write_byte(0x91); //CRC check, 1 dummy byte
wr.write_byte(ops.crc_meta);
uint16_t frames = uint16_t(chip.info.num_frames);
wr.write_byte(uint8_t((frames >> 8) & 0xFF));
wr.write_byte(uint8_t(frames & 0xFF));
size_t bytes_per_frame = (chip.info.bits_per_frame + chip.info.pad_bits_after_frame +
chip.info.pad_bits_before_frame) / 8U;
unique_ptr<uint8_t[]> frame_bytes = make_unique<uint8_t[]>(bytes_per_frame);
for (size_t i = 0; i < frames; i++) {
size_t idx = ops.reversed_frames? (chip.info.num_frames - 1) - i : i;
fill(frame_bytes.get(), frame_bytes.get() + bytes_per_frame, 0x00);
for (int j = 0; j < chip.info.bits_per_frame; j++) {
size_t ofs = j + chip.info.pad_bits_after_frame;
assert(((bytes_per_frame - 1) - (ofs / 8)) < bytes_per_frame);
frame_bytes[(bytes_per_frame - 1) - (ofs / 8)] |=
(chip.cram.bit((chip.info.num_frames - 1) - i, j) & 0x01) << (ofs % 8);
(chip.cram.bit(idx, j) & 0x01) << (ofs % 8);
}
wr.write_bytes(frame_bytes.get(), bytes_per_frame);
wr.insert_crc16();
wr.write_byte(0xFF);
if(ops.crc_after_each_frame) {
wr.insert_crc16();
}

for(size_t j = 0; j < ops.dummy_bytes_after_frame; j++) {
wr.write_byte(0xFF);
}
}
}

if(!ops.crc_after_each_frame) {
wr.insert_crc16();
}

// Post-bitstream space for SECURITY and SED (not used here)
wr.insert_dummy(12);
wr.insert_dummy(ops.security_sed_space);

// Program Usercode
wr.write_byte(uint8_t(BitstreamCommand::ISC_PROGRAM_USERCODE));
wr.write_byte(0x80);
Expand Down Expand Up @@ -821,6 +883,14 @@ Bitstream Bitstream::serialise_chip(const Chip &chip, const map<string, string>
}
wr.insert_crc16();
}

// MachXO2 indeed writes this info twice for some reason...
if (chip.info.family == "MachXO2") {
wr.write_byte(uint8_t(BitstreamCommand::LSC_PROG_CNTRL0));
wr.insert_zeros(3);
wr.write_uint32(ctrl0);
}

// Program DONE
wr.write_byte(uint8_t(BitstreamCommand::ISC_PROGRAM_DONE));
wr.insert_zeros(3);
Expand All @@ -844,7 +914,7 @@ Bitstream Bitstream::serialise_chip_delta_py(const Chip &chip1, const Chip &chip
Bitstream Bitstream::serialise_chip_partial(const Chip &chip, const vector<uint32_t> &frames, const map<string, string> options)
{
BitstreamReadWriter wr;

// Address encoding for partial frame writes
static const map<uint32_t, uint32_t> msb_weights_45k = {
{{0x0000}, { 0*106}},
Expand Down
44 changes: 39 additions & 5 deletions libtrellis/src/Tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,51 @@
#include "Util.hpp"

namespace Trellis {
// Regex to extract row/column from a tile name
// Regexes to extract row/column from a tile name.
static const regex tile_rxcx_re(R"(R(\d+)C(\d+))");

// MachXO2-specific, in order of precedence (otherwise, e.g.
// CENTER_EBR matches r_regex)
static const regex tile_center_re(R"(CENTER(\d+))");
static const regex tile_centerb_re(R"(CENTER_B)");
static const regex tile_centert_re(R"(CENTER_T)");
static const regex tile_centerebr_re(R"(CENTER_EBR(\d+))");
static const regex tile_t_re(R"([A-Za-z0-9_]*T(\d+))");
static const regex tile_b_re(R"([A-Za-z0-9_]*B(\d+))");
static const regex tile_l_re(R"([A-Za-z0-9_]*L(\d+))");
static const regex tile_r_re(R"([A-Za-z0-9_]*R(\d+))");

// Given the zero-indexed max chip_size, return the zero-indexed
// center. Mainly for MachXO2.
// TODO: Make const.
map<pair<int, int>, pair<int, int>> center_map = {
// 1200HC
{make_pair(12, 21), make_pair(6, 12)}
};

// Universal function to get a zero-indexed row/column pair.
pair<int, int> get_row_col_pair_from_chipsize(string name, pair<int, int> chip_size, int bias) {
smatch m;
bool match;

match = regex_search(name, m, tile_rxcx_re);
if(match) {
return make_pair(stoi(m.str(1)), stoi(m.str(2)));
if(regex_search(name, m, tile_rxcx_re)) {
return make_pair(stoi(m.str(1)), stoi(m.str(2)) - bias);
} else if(regex_search(name, m, tile_centert_re)) {
return make_pair(0, center_map[chip_size].second);
} else if(regex_search(name, m, tile_centerb_re)) {
return make_pair(chip_size.first, center_map[chip_size].second);
} else if(regex_search(name, m, tile_centerebr_re)) {
// TODO: This may not apply to devices larger than 1200.
return make_pair(center_map[chip_size].first, stoi(m.str(1)) - bias);
} else if(regex_search(name, m, tile_center_re)) {
return make_pair(stoi(m.str(1)), center_map[chip_size].second);
} else if(regex_search(name, m, tile_t_re)) {
return make_pair(0, stoi(m.str(1)) - bias);
} else if(regex_search(name, m, tile_b_re)) {
return make_pair(chip_size.first, stoi(m.str(1)) - bias);
} else if(regex_search(name, m, tile_l_re)) {
return make_pair(stoi(m.str(1)), 0);
} else if(regex_search(name, m, tile_r_re)) {
return make_pair(stoi(m.str(1)), chip_size.second);
} else {
throw runtime_error(fmt("Could not extract position from " << name));
}
Expand Down
27 changes: 16 additions & 11 deletions tools/connectivity.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#!/usr/bin/env python3
import sys
import pytrellis
import nets
import tiles
import database
if sys.platform in ("win32"):
import pyreadline.rlmain
import readline
import re

Expand All @@ -20,56 +23,58 @@ def main():
pytrellis.load_database(database.get_db_root())
c = pytrellis.Chip("LFE5U-45F")
chip_size = (c.get_max_row(), c.get_max_col())
bias = c.info.col_bias

# Get fan-in to a net
# Returns (source, configurable, loc)
def get_fanin(net):
drivers = []
npos = tiles.pos_from_name(net, chip_size, 0)
npos = tiles.pos_from_name(net, chip_size, bias)
for tile in c.get_all_tiles():
tinf = tile.info
tname = tinf.name
pos = tiles.pos_from_name(tname, chip_size, 0)
pos = tiles.pos_from_name(tname, chip_size, bias)
if abs(pos[0] - npos[0]) >= 10 or abs(pos[1] - npos[1]) >= 10:
continue
if net.startswith("G_"):
tnet = net
else:
tnet = nets.normalise_name(chip_size, tname, net, 0)
tnet = nets.normalise_name(chip_size, tname, net, bias)
tdb = pytrellis.get_tile_bitdata(pytrellis.TileLocator(c.info.family, c.info.name, tinf.type))
try:
mux = tdb.get_mux_data_for_sink(tnet)
for src in mux.get_sources():
drivers.append((nets.canonicalise_name(chip_size, tname, src, 0), True, tname))
drivers.append((nets.canonicalise_name(chip_size, tname, src, bias), True, tname))
except IndexError:
pass
for fc in tdb.get_fixed_conns():
if fc.sink == tnet:
drivers.append((nets.canonicalise_name(chip_size, tname, fc.source, 0), False, tname))
drivers.append((nets.canonicalise_name(chip_size, tname, fc.source, bias), False, tname))
return drivers

# Get fan-out of a net
# Returns (dest, configurable, loc)
def get_fanout(net):
drivers = []
npos = tiles.pos_from_name(net, chip_size, 0)
npos = tiles.pos_from_name(net, chip_size, bias)
for tile in c.get_all_tiles():
tinf = tile.info
tname = tinf.name
pos = tiles.pos_from_name(tname, chip_size, 0)
pos = tiles.pos_from_name(tname, chip_size, bias)
if abs(pos[0] - npos[0]) >= 12 or abs(pos[1] - npos[1]) >= 12:
continue
if net.startswith("G_"):
tnet = net
else:
tnet = nets.normalise_name(chip_size, tname, net, 0)
tnet = nets.normalise_name(chip_size, tname, net, bias)
tdb = pytrellis.get_tile_bitdata(pytrellis.TileLocator(c.info.family, c.info.name, tinf.type))
for sink in tdb.get_sinks():
mux = tdb.get_mux_data_for_sink(sink)
if tnet in mux.arcs:
drivers.append((nets.canonicalise_name(chip_size, tname, sink, 0), True, tname))
drivers.append((nets.canonicalise_name(chip_size, tname, sink, bias), True, tname))
for fc in tdb.get_fixed_conns():
if fc.source == tnet:
drivers.append((nets.canonicalise_name(chip_size, tname, fc.sink, 0), False, tname))
drivers.append((nets.canonicalise_name(chip_size, tname, fc.sink, bias), False, tname))
return drivers


Expand Down Expand Up @@ -105,7 +110,7 @@ def get_nets_at(loc):
def completer(str, idx):
if not tile_net_re.match(str):
return None
loc = tiles.pos_from_name(str, chip_size, 0)
loc = tiles.pos_from_name(str, chip_size, bias)
nets = get_nets_at(loc)
for n in nets:
if n.startswith(str):
Expand Down

0 comments on commit 7283c5f

Please sign in to comment.