diff options
Diffstat (limited to 'generic/viaduct/example')
-rw-r--r-- | generic/viaduct/example/.gitignore | 1 | ||||
-rw-r--r-- | generic/viaduct/example/constids.inc | 14 | ||||
-rw-r--r-- | generic/viaduct/example/example.cc | 306 | ||||
-rw-r--r-- | generic/viaduct/example/example_map.v | 12 | ||||
-rw-r--r-- | generic/viaduct/example/example_prims.v | 35 | ||||
-rw-r--r-- | generic/viaduct/example/synth_viaduct_example.tcl | 24 | ||||
-rwxr-xr-x | generic/viaduct/example/viaduct_example.sh | 6 |
7 files changed, 398 insertions, 0 deletions
diff --git a/generic/viaduct/example/.gitignore b/generic/viaduct/example/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/generic/viaduct/example/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/generic/viaduct/example/constids.inc b/generic/viaduct/example/constids.inc new file mode 100644 index 00000000..b40d5be8 --- /dev/null +++ b/generic/viaduct/example/constids.inc @@ -0,0 +1,14 @@ +X(LUT4) +X(DFF) +X(CLK) +X(D) +X(F) +X(Q) +X(INBUF) +X(OUTBUF) +X(I) +X(EN) +X(O) +X(IOB) +X(PAD) +X(INIT) diff --git a/generic/viaduct/example/example.cc b/generic/viaduct/example/example.cc new file mode 100644 index 00000000..3d1c201c --- /dev/null +++ b/generic/viaduct/example/example.cc @@ -0,0 +1,306 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2021 gatecat <gatecat@ds0.me> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "log.h" +#include "nextpnr.h" +#include "util.h" +#include "viaduct_api.h" +#include "viaduct_helpers.h" + +#define GEN_INIT_CONSTIDS +#define VIADUCT_CONSTIDS "viaduct/example/constids.inc" +#include "viaduct_constids.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +struct ExampleImpl : ViaductAPI +{ + ~ExampleImpl(){}; + void init(Context *ctx) override + { + init_uarch_constids(ctx); + ViaductAPI::init(ctx); + h.init(ctx); + init_wires(); + init_bels(); + init_pips(); + } + + void pack() override + { + // Trim nextpnr IOBs - assume IO buffer insertion has been done in synthesis + const pool<CellTypePort> top_ports{ + CellTypePort(id_INBUF, id_PAD), + CellTypePort(id_OUTBUF, id_PAD), + }; + h.remove_nextpnr_iobs(top_ports); + // Replace constants with LUTs + const dict<IdString, Property> vcc_params = {{id_INIT, Property(0xFFFF, 16)}}; + const dict<IdString, Property> gnd_params = {{id_INIT, Property(0x0000, 16)}}; + h.replace_constants(CellTypePort(id_LUT4, id_F), CellTypePort(id_LUT4, id_F), vcc_params, gnd_params); + // Constrain directly connected LUTs and FFs together to use dedicated resources + int lutffs = h.constrain_cell_pairs(pool<CellTypePort>{{id_LUT4, id_F}}, pool<CellTypePort>{{id_DFF, id_D}}, 1); + log_info("Constrained %d LUTFF pairs.\n", lutffs); + } + + void prePlace() override { assign_cell_info(); } + + bool isBelLocationValid(BelId bel) const override + { + Loc l = ctx->getBelLocation(bel); + if (is_io(l.x, l.y)) { + return true; + } else { + return slice_valid(l.x, l.y, l.z / 2); + } + } + + private: + ViaductHelpers h; + // Configuration + // Grid size including IOBs at edges + const int X = 32, Y = 32; + // SLICEs per tile + const int N = 8; + // LUT input count + const int K = 4; + // Number of local wires + const int Wl = N * (K + 1) + 8; + // 1/Fc for bel input wire pips; local wire pips and neighbour pips + const int Si = 4, Sq = 4, Sl = 8; + + // For fast wire lookups + struct TileWires + { + std::vector<WireId> clk, q, f, d, i; + std::vector<WireId> l; + std::vector<WireId> pad; + }; + + std::vector<std::vector<TileWires>> wires_by_tile; + + // Create wires to attach to bels and pips + void init_wires() + { + log_info("Creating wires...\n"); + wires_by_tile.resize(Y); + for (int y = 0; y < Y; y++) { + auto &row_wires = wires_by_tile.at(y); + row_wires.resize(X); + for (int x = 0; x < X; x++) { + auto &w = row_wires.at(x); + for (int z = 0; z < N; z++) { + // Clock input + w.clk.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("CLK%d", z))), ctx->id("CLK"), x, y)); + // FF input + w.d.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("D%d", z))), ctx->id("D"), x, y)); + // FF and LUT outputs + w.q.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("Q%d", z))), ctx->id("Q"), x, y)); + w.f.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("F%d", z))), ctx->id("F"), x, y)); + // LUT inputs + for (int i = 0; i < K; i++) + w.i.push_back( + ctx->addWire(h.xy_id(x, y, ctx->id(stringf("L%dI%d", z, i))), ctx->id("I"), x, y)); + } + // Local wires + for (int l = 0; l < Wl; l++) + w.l.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("LOCAL%d", l))), ctx->id("LOCAL"), x, y)); + // Pad wires for IO + if (is_io(x, y) && x != y) + for (int z = 0; z < 2; z++) + w.pad.push_back(ctx->addWire(h.xy_id(x, y, ctx->id(stringf("PAD%d", z))), id_PAD, x, y)); + } + } + } + bool is_io(int x, int y) const + { + // IO are on the edges of the device + return (x == 0) || (x == (X - 1)) || (y == 0) || (y == (Y - 1)); + } + // Create IO bels in an IO tile + void add_io_bels(int x, int y) + { + auto &w = wires_by_tile.at(y).at(x); + for (int z = 0; z < 2; z++) { + BelId b = ctx->addBel(h.xy_id(x, y, ctx->id(stringf("IO%d", z))), id_IOB, Loc(x, y, z), false, false); + ctx->addBelInout(b, id_PAD, w.pad.at(z)); + ctx->addBelInput(b, id_I, w.i.at(z * K + 0)); + ctx->addBelInput(b, id_EN, w.i.at(z * K + 1)); + ctx->addBelOutput(b, id_O, w.q.at(z)); + } + } + PipId add_pip(Loc loc, WireId src, WireId dst, delay_t delay = 0.05) + { + IdStringList name = IdStringList::concat(ctx->getWireName(dst), ctx->getWireName(src)); + return ctx->addPip(name, ctx->id("PIP"), src, dst, delay, loc); + } + // Create LUT and FF bels in a logic tile + void add_slice_bels(int x, int y) + { + auto &w = wires_by_tile.at(y).at(x); + for (int z = 0; z < N; z++) { + // Create LUT bel + BelId lut = ctx->addBel(h.xy_id(x, y, ctx->id(stringf("SLICE%d_LUT", z))), id_LUT4, Loc(x, y, z * 2), false, + false); + for (int k = 0; k < K; k++) + ctx->addBelInput(lut, ctx->id(stringf("I[%d]", k)), w.i.at(z * K + k)); + ctx->addBelOutput(lut, id_F, w.f.at(z)); + // FF data can come from LUT output or LUT I3 + add_pip(Loc(x, y, 0), w.f.at(z), w.d.at(z)); + add_pip(Loc(x, y, 0), w.i.at(z * K + (K - 1)), w.d.at(z)); + // Create DFF bel + BelId dff = ctx->addBel(h.xy_id(x, y, ctx->id(stringf("SLICE%d_FF", z))), id_DFF, Loc(x, y, z * 2 + 1), + false, false); + ctx->addBelInput(dff, id_CLK, w.clk.at(z)); + ctx->addBelInput(dff, id_D, w.d.at(z)); + ctx->addBelOutput(dff, id_Q, w.q.at(z)); + } + } + // Create bels according to tile type + void init_bels() + { + log_info("Creating bels...\n"); + for (int y = 0; y < Y; y++) { + for (int x = 0; x < X; x++) { + if (is_io(x, y)) { + if (x == y) + continue; // don't put IO in corners + add_io_bels(x, y); + } else { + add_slice_bels(x, y); + } + } + } + } + + // Create PIPs inside a tile; following an example synthetic routing pattern + void add_tile_pips(int x, int y) + { + auto &w = wires_by_tile.at(y).at(x); + Loc loc(x, y, 0); + auto create_input_pips = [&](WireId dst, int offset, int skip) { + for (int i = (offset % skip); i < Wl; i += skip) + add_pip(loc, w.l.at(i), dst, 0.05); + }; + for (int z = 0; z < N; z++) { + create_input_pips(w.clk.at(z), 0, Si); + for (int k = 0; k < K; k++) + create_input_pips(w.i.at(z * K + k), k, Si); + } + auto create_output_pips = [&](WireId dst, int offset, int skip) { + for (int z = (offset % skip); z < N; z += skip) { + add_pip(loc, w.f.at(z), dst, 0.05); + add_pip(loc, w.q.at(z), dst, 0.05); + } + }; + auto create_neighbour_pips = [&](WireId dst, int nx, int ny, int offset, int skip) { + if (nx < 0 || nx >= X) + return; + if (ny < 0 || ny >= Y) + return; + auto &nw = wires_by_tile.at(ny).at(nx); + for (int i = (offset % skip); i < Wl; i += skip) + add_pip(loc, dst, nw.l.at(i), 0.1); + }; + for (int i = 0; i < Wl; i++) { + WireId dst = w.l.at(i); + create_output_pips(dst, i % Sq, Sq); + create_neighbour_pips(dst, x - 1, y - 1, (i + 1) % Sl, Sl); + create_neighbour_pips(dst, x - 1, y, (i + 2) % Sl, Sl); + create_neighbour_pips(dst, x - 1, y + 1, (i + 3) % Sl, Sl); + create_neighbour_pips(dst, x, y - 1, (i + 4) % Sl, Sl); + create_neighbour_pips(dst, x, y + 1, (i + 5) % Sl, Sl); + create_neighbour_pips(dst, x + 1, y - 1, (i + 6) % Sl, Sl); + create_neighbour_pips(dst, x + 1, y, (i + 7) % Sl, Sl); + create_neighbour_pips(dst, x + 1, y + 1, (i + 8) % Sl, Sl); + } + } + void init_pips() + { + log_info("Creating pips...\n"); + for (int y = 0; y < Y; y++) + for (int x = 0; x < X; x++) + add_tile_pips(x, y); + } + // Validity checking + struct ExampleCellInfo + { + const NetInfo *lut_f = nullptr, *ff_d = nullptr; + bool lut_i3_used = false; + }; + std::vector<ExampleCellInfo> fast_cell_info; + void assign_cell_info() + { + fast_cell_info.resize(ctx->cells.size()); + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + auto &fc = fast_cell_info.at(ci->flat_index); + if (ci->type == id_LUT4) { + fc.lut_f = get_net_or_empty(ci, id_F); + fc.lut_i3_used = (get_net_or_empty(ci, ctx->id(stringf("I[%d]", K - 1))) != nullptr); + } else if (ci->type == id_DFF) { + fc.ff_d = get_net_or_empty(ci, id_D); + } + } + } + bool slice_valid(int x, int y, int z) const + { + const CellInfo *lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2))); + const CellInfo *ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2 + 1))); + if (!lut || !ff) + return true; // always valid if only LUT or FF used + const auto &lut_data = fast_cell_info.at(lut->flat_index); + const auto &ff_data = fast_cell_info.at(ff->flat_index); + // In our example arch; the FF D can either be driven from LUT F or LUT I3 + // so either; FF D must equal LUT F or LUT I3 must be unused + if (ff_data.ff_d == lut_data.lut_f) + return true; + if (lut_data.lut_i3_used) + return false; + return true; + } + // Bel bucket functions + IdString getBelBucketForCellType(IdString cell_type) const override + { + if (cell_type.in(id_INBUF, id_OUTBUF)) + return id_IOB; + return cell_type; + } + bool isValidBelForCellType(IdString cell_type, BelId bel) const override + { + IdString bel_type = ctx->getBelType(bel); + if (bel_type == id_IOB) + return cell_type.in(id_INBUF, id_OUTBUF); + else + return (bel_type == cell_type); + } +}; + +struct ExampleArch : ViaductArch +{ + ExampleArch() : ViaductArch("example"){}; + std::unique_ptr<ViaductAPI> create(const dict<std::string, std::string> &args) + { + return std::make_unique<ExampleImpl>(); + } +} exampleArch; +} // namespace + +NEXTPNR_NAMESPACE_END diff --git a/generic/viaduct/example/example_map.v b/generic/viaduct/example/example_map.v new file mode 100644 index 00000000..f701f0ed --- /dev/null +++ b/generic/viaduct/example/example_map.v @@ -0,0 +1,12 @@ +module \$lut (A, Y); + parameter WIDTH = 0; + parameter LUT = 0; + input [WIDTH-1:0] A; + output Y; + + localparam rep = 1<<(4-WIDTH); + + LUT4 #(.INIT({rep{LUT}})) _TECHMAP_REPLACE_ (.I(A), .F(Y)); +endmodule + +module \$_DFF_P_ (input D, C, output Q); DFF _TECHMAP_REPLACE_ (.D(D), .Q(Q), .CLK(C)); endmodule diff --git a/generic/viaduct/example/example_prims.v b/generic/viaduct/example/example_prims.v new file mode 100644 index 00000000..d2813129 --- /dev/null +++ b/generic/viaduct/example/example_prims.v @@ -0,0 +1,35 @@ +module LUT4 #( + parameter [15:0] INIT = 0 +) ( + input [3:0] I, + output F +); + wire [7:0] s3 = I[3] ? INIT[15:8] : INIT[7:0]; + wire [3:0] s2 = I[2] ? s3[ 7:4] : s3[3:0]; + wire [1:0] s1 = I[1] ? s2[ 3:2] : s2[1:0]; + assign F = I[0] ? s1[1] : s1[0]; +endmodule + +module DFF ( + input CLK, D, + output reg Q +); + initial Q = 1'b0; + always @(posedge CLK) + Q <= D; +endmodule + +module INBUF ( + input PAD, + output O, +); + assign O = PAD; +endmodule + +module OUTBUF ( + output PAD, + input I, +); + assign PAD = I; +endmodule + diff --git a/generic/viaduct/example/synth_viaduct_example.tcl b/generic/viaduct/example/synth_viaduct_example.tcl new file mode 100644 index 00000000..a9d18f56 --- /dev/null +++ b/generic/viaduct/example/synth_viaduct_example.tcl @@ -0,0 +1,24 @@ +# Usage +# tcl synth_viaduct_example.tcl {out.json} + +yosys read_verilog -lib [file dirname [file normalize $argv0]]/example_prims.v +yosys hierarchy -check +yosys proc +yosys flatten +yosys tribuf -logic +yosys deminout +yosys synth -run coarse +yosys memory_map +yosys opt -full +yosys iopadmap -bits -inpad INBUF O:PAD -outpad OUTBUF I:PAD +yosys techmap -map +/techmap.v +yosys opt -fast +yosys dfflegalize -cell \$_DFF_P_ 0 +yosys abc -lut 4 -dress +yosys clean +yosys techmap -map [file dirname [file normalize $argv0]]/example_map.v +yosys clean +yosys hierarchy -check +yosys stat + +if {$argc > 0} { yosys write_json [lindex $argv 0] } diff --git a/generic/viaduct/example/viaduct_example.sh b/generic/viaduct/example/viaduct_example.sh new file mode 100755 index 00000000..0842df20 --- /dev/null +++ b/generic/viaduct/example/viaduct_example.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -ex +# Run synthesis +yosys -p "tcl synth_viaduct_example.tcl blinky.json" ../../examples/blinky.v +# Run PnR +${NEXTPNR:-../../../build/nextpnr-generic} --uarch example --json blinky.json |