Skip to content

Commit 5398adb

Browse files
authored
Merge pull request verilog-to-routing#1925 from antmicro/acom/fpga-interchange-device-upstream
libs: arch: interchange: add basic device information
2 parents f92cfeb + a4226a0 commit 5398adb

File tree

2 files changed

+335
-0
lines changed

2 files changed

+335
-0
lines changed

libs/libarchfpga/src/read_fpga_interchange_arch.cpp

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,82 @@ using namespace DeviceResources;
3434
using namespace LogicalNetlist;
3535
using namespace capnp;
3636

37+
/**
38+
* @brief The FPGA interchange timing model includes three different corners (min, typ and max) for each of the two
39+
* speed_models (slow and fast).
40+
*
41+
* Timing data can be found on PIPs, nodes, site pins and bel pins.
42+
* This function retrieves the timing value based on the wanted speed model and the wanted corner.
43+
*
44+
* More information on the FPGA Interchange timing model can be found here:
45+
* - https://github.com/chipsalliance/fpga-interchange-schema/blob/main/interchange/DeviceResources.capnp
46+
*/
47+
static float get_corner_value(Device::CornerModel::Reader model, const char* speed_model, const char* value) {
48+
bool slow_model = std::string(speed_model) == std::string("slow");
49+
bool fast_model = std::string(speed_model) == std::string("fast");
50+
51+
bool min_corner = std::string(value) == std::string("min");
52+
bool typ_corner = std::string(value) == std::string("typ");
53+
bool max_corner = std::string(value) == std::string("max");
54+
55+
if (!slow_model && !fast_model) {
56+
archfpga_throw("", __LINE__,
57+
"Wrong speed model `%s`. Expected `slow` or `fast`\n", speed_model);
58+
}
59+
60+
if (!min_corner && !typ_corner && !max_corner) {
61+
archfpga_throw("", __LINE__,
62+
"Wrong corner model `%s`. Expected `min`, `typ` or `max`\n", value);
63+
}
64+
65+
bool has_fast = model.getFast().hasFast();
66+
bool has_slow = model.getSlow().hasSlow();
67+
68+
if (slow_model && has_slow) {
69+
auto half = model.getSlow().getSlow();
70+
if (min_corner && half.getMin().isMin()) {
71+
return half.getMin().getMin();
72+
} else if (typ_corner && half.getTyp().isTyp()) {
73+
return half.getTyp().getTyp();
74+
} else if (max_corner && half.getMax().isMax()) {
75+
return half.getMax().getMax();
76+
} else {
77+
if (half.getMin().isMin()) {
78+
return half.getMin().getMin();
79+
} else if (half.getTyp().isTyp()) {
80+
return half.getTyp().getTyp();
81+
} else if (half.getMax().isMax()) {
82+
return half.getMax().getMax();
83+
} else {
84+
archfpga_throw("", __LINE__,
85+
"Invalid speed model %s. No value found!\n", speed_model);
86+
}
87+
}
88+
} else if (fast_model && has_fast) {
89+
auto half = model.getFast().getFast();
90+
if (min_corner && half.getMin().isMin()) {
91+
return half.getMin().getMin();
92+
} else if (typ_corner && half.getTyp().isTyp()) {
93+
return half.getTyp().getTyp();
94+
} else if (max_corner && half.getMax().isMax()) {
95+
return half.getMax().getMax();
96+
} else {
97+
if (half.getMin().isMin()) {
98+
return half.getMin().getMin();
99+
} else if (half.getTyp().isTyp()) {
100+
return half.getTyp().getTyp();
101+
} else if (half.getMax().isMax()) {
102+
return half.getMax().getMax();
103+
} else {
104+
archfpga_throw("", __LINE__,
105+
"Invalid speed model %s. No value found!\n", speed_model);
106+
}
107+
}
108+
}
109+
110+
return 0.;
111+
}
112+
37113
struct ArchReader {
38114
public:
39115
ArchReader(t_arch* arch, Device::Reader& arch_reader, const char* arch_file, std::vector<t_physical_tile_type>& phys_types, std::vector<t_logical_block_type>& logical_types)
@@ -47,6 +123,11 @@ struct ArchReader {
47123

48124
void read_arch() {
49125
process_models();
126+
process_device();
127+
128+
process_layout();
129+
process_switches();
130+
process_segments();
50131
}
51132

52133
private:
@@ -163,6 +244,234 @@ struct ArchReader {
163244
}
164245
}
165246
}
247+
248+
// Layout Processing
249+
void process_layout() {
250+
auto strList = ar_.getStrList();
251+
auto tileList = ar_.getTileList();
252+
auto tileTypeList = ar_.getTileTypeList();
253+
t_grid_def grid_def;
254+
255+
grid_def.width = grid_def.height = 0;
256+
for (auto tile : tileList) {
257+
grid_def.width = std::max(grid_def.width, tile.getCol() + 1);
258+
grid_def.height = std::max(grid_def.height, tile.getRow() + 1);
259+
}
260+
261+
grid_def.grid_type = GridDefType::FIXED;
262+
std::string name = std::string(ar_.getName());
263+
264+
if (name == "auto") {
265+
// At the moment, the interchange specifies fixed-layout only architectures,
266+
// and allowing for auto-sizing could potentially be implemented later on
267+
// to allow for experimentation on new architectures.
268+
// For the time being the layout is restricted to be only fixed.
269+
archfpga_throw(arch_file_, __LINE__,
270+
"The name auto is reserved for auto-size layouts; please choose another name");
271+
}
272+
273+
grid_def.name = name;
274+
for (auto tile : tileList) {
275+
t_metadata_dict data;
276+
std::string tile_prefix(strList[tile.getName()].cStr());
277+
auto tileType = tileTypeList[tile.getType()];
278+
std::string tile_type(strList[tileType.getName()].cStr());
279+
280+
size_t pos = tile_prefix.find(tile_type);
281+
if (pos != std::string::npos && pos == 0)
282+
tile_prefix.erase(pos, tile_type.length() + 1);
283+
data.add(arch_->strings.intern_string(vtr::string_view("fasm_prefix")),
284+
arch_->strings.intern_string(vtr::string_view(tile_prefix.c_str())));
285+
t_grid_loc_def single(tile_type, 1);
286+
single.x.start_expr = tile.getCol();
287+
single.y.start_expr = tile.getRow();
288+
single.x.end_expr = single.x.start_expr + " + w - 1";
289+
single.y.end_expr = single.y.start_expr + " + h - 1";
290+
single.owned_meta = std::make_unique<t_metadata_dict>(data);
291+
single.meta = single.owned_meta.get();
292+
grid_def.loc_defs.emplace_back(std::move(single));
293+
}
294+
295+
arch_->grid_layouts.emplace_back(std::move(grid_def));
296+
}
297+
298+
void process_device() {
299+
/*
300+
* The generic architecture data is not currently available in the interchange format
301+
* therefore, for a very initial implementation, the values are taken from the ones
302+
* used primarly in the Xilinx series7 devices, generated using SymbiFlow.
303+
*
304+
* As the interchange format develops further, with possibly more details, this function can
305+
* become dynamic, allowing for different parameters for the different architectures.
306+
*
307+
* FIXME: This will require to be dynamically assigned, and a suitable representation added
308+
* to the FPGA interchange device schema.
309+
*/
310+
arch_->R_minW_nmos = 6065.520020;
311+
arch_->R_minW_pmos = 18138.500000;
312+
arch_->grid_logic_tile_area = 14813.392;
313+
arch_->Chans.chan_x_dist.type = UNIFORM;
314+
arch_->Chans.chan_x_dist.peak = 1;
315+
arch_->Chans.chan_x_dist.width = 0;
316+
arch_->Chans.chan_x_dist.xpeak = 0;
317+
arch_->Chans.chan_x_dist.dc = 0;
318+
arch_->Chans.chan_y_dist.type = UNIFORM;
319+
arch_->Chans.chan_y_dist.peak = 1;
320+
arch_->Chans.chan_y_dist.width = 0;
321+
arch_->Chans.chan_y_dist.xpeak = 0;
322+
arch_->Chans.chan_y_dist.dc = 0;
323+
arch_->ipin_cblock_switch_name = std::string("generic");
324+
arch_->SBType = WILTON;
325+
arch_->Fs = 3;
326+
default_fc_.specified = true;
327+
default_fc_.in_value_type = e_fc_value_type::FRACTIONAL;
328+
default_fc_.in_value = 1.0;
329+
default_fc_.out_value_type = e_fc_value_type::FRACTIONAL;
330+
default_fc_.out_value = 1.0;
331+
}
332+
333+
void process_switches() {
334+
std::set<std::pair<bool, uint32_t>> pip_timing_models;
335+
for (auto tile_type : ar_.getTileTypeList()) {
336+
for (auto pip : tile_type.getPips()) {
337+
pip_timing_models.insert(std::pair<bool, uint32_t>(pip.getBuffered21(), pip.getTiming()));
338+
if (!pip.getDirectional())
339+
pip_timing_models.insert(std::pair<bool, uint32_t>(pip.getBuffered20(), pip.getTiming()));
340+
}
341+
}
342+
343+
auto timing_data = ar_.getPipTimings();
344+
345+
std::vector<std::pair<bool, uint32_t>> pip_timing_models_list;
346+
pip_timing_models_list.reserve(pip_timing_models.size());
347+
348+
for (auto entry : pip_timing_models) {
349+
pip_timing_models_list.push_back(entry);
350+
}
351+
352+
auto num_switches = pip_timing_models.size() + 2;
353+
std::string switch_name;
354+
355+
arch_->num_switches = num_switches;
356+
auto* switches = arch_->Switches;
357+
358+
if (num_switches > 0) {
359+
switches = new t_arch_switch_inf[num_switches];
360+
}
361+
362+
float R, Cin, Cint, Cout, Tdel;
363+
for (int i = 0; i < (int)num_switches; ++i) {
364+
t_arch_switch_inf& as = switches[i];
365+
366+
R = Cin = Cint = Cout = Tdel = 0.0;
367+
SwitchType type;
368+
369+
if (i == 0) {
370+
switch_name = "short";
371+
type = SwitchType::SHORT;
372+
R = 0.0;
373+
} else if (i == 1) {
374+
switch_name = "generic";
375+
type = SwitchType::MUX;
376+
R = 0.0;
377+
} else {
378+
auto entry = pip_timing_models_list[i - 2];
379+
auto model = timing_data[entry.second];
380+
std::stringstream name;
381+
std::string mux_type_string = entry.first ? "mux_" : "passGate_";
382+
name << mux_type_string;
383+
384+
// FIXME: allow to dynamically choose different speed models and corners
385+
R = get_corner_value(model.getOutputResistance(), "slow", "min");
386+
name << "R" << std::scientific << R;
387+
388+
Cin = get_corner_value(model.getInputCapacitance(), "slow", "min");
389+
name << "Cin" << std::scientific << Cin;
390+
391+
Cout = get_corner_value(model.getOutputCapacitance(), "slow", "min");
392+
name << "Cout" << std::scientific << Cout;
393+
394+
if (entry.first) {
395+
Cint = get_corner_value(model.getInternalCapacitance(), "slow", "min");
396+
name << "Cinternal" << std::scientific << Cint;
397+
}
398+
399+
Tdel = get_corner_value(model.getInternalDelay(), "slow", "min");
400+
name << "Tdel" << std::scientific << Tdel;
401+
402+
switch_name = name.str();
403+
type = entry.first ? SwitchType::MUX : SwitchType::PASS_GATE;
404+
}
405+
406+
/* Should never happen */
407+
if (switch_name == std::string(VPR_DELAYLESS_SWITCH_NAME)) {
408+
archfpga_throw(arch_file_, __LINE__,
409+
"Switch name '%s' is a reserved name for VPR internal usage!", switch_name.c_str());
410+
}
411+
412+
as.name = vtr::strdup(switch_name.c_str());
413+
as.set_type(type);
414+
as.mux_trans_size = as.type() == SwitchType::MUX ? 1 : 0;
415+
416+
as.R = R;
417+
as.Cin = Cin;
418+
as.Cout = Cout;
419+
as.Cinternal = Cint;
420+
as.set_Tdel(t_arch_switch_inf::UNDEFINED_FANIN, Tdel);
421+
422+
if (as.type() == SwitchType::SHORT || as.type() == SwitchType::PASS_GATE) {
423+
as.buf_size_type = BufferSize::ABSOLUTE;
424+
as.buf_size = 0;
425+
as.power_buffer_type = POWER_BUFFER_TYPE_ABSOLUTE_SIZE;
426+
as.power_buffer_size = 0.;
427+
} else {
428+
as.buf_size_type = BufferSize::AUTO;
429+
as.buf_size = 0.;
430+
as.power_buffer_type = POWER_BUFFER_TYPE_AUTO;
431+
}
432+
}
433+
}
434+
435+
void process_segments() {
436+
auto strList = ar_.getStrList();
437+
438+
// Segment names will be taken from wires connected to pips
439+
// They are good representation for nodes
440+
std::set<uint32_t> wire_names;
441+
for (auto tile_type : ar_.getTileTypeList()) {
442+
auto wires = tile_type.getWires();
443+
for (auto pip : tile_type.getPips()) {
444+
wire_names.insert(wires[pip.getWire0()]);
445+
wire_names.insert(wires[pip.getWire1()]);
446+
}
447+
}
448+
int num_seg = wire_names.size();
449+
arch_->Segments.resize(num_seg);
450+
uint32_t index = 0;
451+
for (auto i : wire_names) {
452+
// Use default values as we will populate rr_graph with correct values
453+
// This segments are just declaration of future use
454+
arch_->Segments[index].name = std::string(strList[i]);
455+
arch_->Segments[index].length = 1;
456+
arch_->Segments[index].frequency = 1;
457+
arch_->Segments[index].Rmetal = 0;
458+
arch_->Segments[index].Cmetal = 0;
459+
arch_->Segments[index].parallel_axis = BOTH_AXIS;
460+
461+
// TODO: Only bi-directional segments are created, but it the interchange format
462+
// has directionality information on PIPs, which may be used to infer the
463+
// segments' directonality.
464+
arch_->Segments[index].directionality = BI_DIRECTIONAL;
465+
arch_->Segments[index].arch_wire_switch = 1;
466+
arch_->Segments[index].arch_opin_switch = 1;
467+
arch_->Segments[index].cb.resize(1);
468+
arch_->Segments[index].cb[0] = true;
469+
arch_->Segments[index].sb.resize(2);
470+
arch_->Segments[index].sb[0] = true;
471+
arch_->Segments[index].sb[1] = true;
472+
++index;
473+
}
474+
}
166475
};
167476

168477
void FPGAInterchangeReadArch(const char* FPGAInterchangeDeviceFile,

vpr/test/test_interchange_device.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,30 @@ TEST_CASE("read_interchange_models", "[vpr]") {
4141
REQUIRE(lib_models.size() == 0);
4242
}
4343

44+
TEST_CASE("read_interchange_layout", "[vpr]") {
45+
t_arch arch;
46+
std::vector<t_physical_tile_type> physical_tile_types;
47+
std::vector<t_logical_block_type> logical_block_types;
48+
49+
FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types);
50+
51+
auto& gd = arch.grid_layouts[0];
52+
REQUIRE(gd.grid_type == GridDefType::FIXED);
53+
REQUIRE(gd.height == 10);
54+
REQUIRE(gd.width == 10);
55+
56+
std::unordered_map<std::string, bool> tile_types({{"NULL", false}, {"PWR", false}, {"IOB", false}, {"CLB", false}});
57+
for (auto& loc : gd.loc_defs) {
58+
auto ret = tile_types.find(loc.block_type);
59+
REQUIRE(ret != tile_types.end());
60+
REQUIRE(loc.priority == 1);
61+
62+
ret->second = true;
63+
}
64+
65+
for (auto type : tile_types) {
66+
CHECK(type.second);
67+
}
68+
}
69+
4470
} // namespace

0 commit comments

Comments
 (0)