svg: Basic SVG graphics rendering
Signed-off-by: David Shah <dave@ds0.me>
This commit is contained in:
parent
e0b4f0ee63
commit
b6158f94f6
@ -114,3 +114,6 @@ fn_wrapper_0a<Context, decltype(&Context::getChipName), &Context::getChipName, p
|
|||||||
ctx_cls, "getChipName");
|
ctx_cls, "getChipName");
|
||||||
fn_wrapper_0a<Context, decltype(&Context::archId), &Context::archId, conv_to_str<IdString>>::def_wrap(ctx_cls,
|
fn_wrapper_0a<Context, decltype(&Context::archId), &Context::archId, conv_to_str<IdString>>::def_wrap(ctx_cls,
|
||||||
"archId");
|
"archId");
|
||||||
|
|
||||||
|
fn_wrapper_2a_v<Context, decltype(&Context::writeSVG), &Context::writeSVG, pass_through<std::string>,
|
||||||
|
pass_through<std::string>>::def_wrap(ctx_cls, "writeSVG");
|
||||||
|
@ -158,6 +158,9 @@ po::options_description CommandHandler::getGeneralOptions()
|
|||||||
general.add_options()("sdf", po::value<std::string>(), "SDF delay back-annotation file to write");
|
general.add_options()("sdf", po::value<std::string>(), "SDF delay back-annotation file to write");
|
||||||
general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator");
|
general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator");
|
||||||
|
|
||||||
|
general.add_options()("placed-svg", po::value<std::string>(), "write render of placement to SVG file");
|
||||||
|
general.add_options()("routed-svg", po::value<std::string>(), "write render of routing to SVG file");
|
||||||
|
|
||||||
return general;
|
return general;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,6 +339,8 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
|
|||||||
if (!ctx->place() && !ctx->force)
|
if (!ctx->place() && !ctx->force)
|
||||||
log_error("Placing design failed.\n");
|
log_error("Placing design failed.\n");
|
||||||
ctx->check();
|
ctx->check();
|
||||||
|
if (vm.count("placed-svg"))
|
||||||
|
ctx->writeSVG(vm["placed-svg"].as<std::string>(), "scale=50 hide_routing");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (do_route) {
|
if (do_route) {
|
||||||
@ -343,6 +348,8 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
|
|||||||
if (!ctx->route() && !ctx->force)
|
if (!ctx->route() && !ctx->force)
|
||||||
log_error("Routing design failed.\n");
|
log_error("Routing design failed.\n");
|
||||||
run_script_hook("post-route");
|
run_script_hook("post-route");
|
||||||
|
if (vm.count("routed-svg"))
|
||||||
|
ctx->writeSVG(vm["routed-svg"].as<std::string>(), "scale=500");
|
||||||
}
|
}
|
||||||
|
|
||||||
customBitstream(ctx.get());
|
customBitstream(ctx.get());
|
||||||
|
@ -871,6 +871,11 @@ struct Context : Arch, DeterministicRNG
|
|||||||
|
|
||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
|
// provided by svg.cc
|
||||||
|
void writeSVG(const std::string &filename, const std::string &flags = "") const;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
uint32_t checksum() const;
|
uint32_t checksum() const;
|
||||||
|
|
||||||
void check() const;
|
void check() const;
|
||||||
|
150
common/svg.cc
Normal file
150
common/svg.cc
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* nextpnr -- Next Generation Place and Route
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 David Shah <dave@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 <boost/algorithm/string.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
#include "log.h"
|
||||||
|
#include "nextpnr.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
namespace {
|
||||||
|
struct SVGWriter
|
||||||
|
{
|
||||||
|
const Context *ctx;
|
||||||
|
std::ostream &out;
|
||||||
|
float scale = 500.0;
|
||||||
|
bool hide_inactive = false;
|
||||||
|
SVGWriter(const Context *ctx, std::ostream &out) : ctx(ctx), out(out){};
|
||||||
|
const char *get_stroke_colour(GraphicElement::style_t style)
|
||||||
|
{
|
||||||
|
switch (style) {
|
||||||
|
case GraphicElement::STYLE_GRID:
|
||||||
|
return "#CCC";
|
||||||
|
case GraphicElement::STYLE_FRAME:
|
||||||
|
return "#808080";
|
||||||
|
case GraphicElement::STYLE_INACTIVE:
|
||||||
|
return "#C0C0C0";
|
||||||
|
case GraphicElement::STYLE_ACTIVE:
|
||||||
|
return "#FF3030";
|
||||||
|
default:
|
||||||
|
return "#000";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_decal(const DecalXY &dxy)
|
||||||
|
{
|
||||||
|
for (const auto &el : ctx->getDecalGraphics(dxy.decal)) {
|
||||||
|
if (el.style == GraphicElement::STYLE_HIDDEN ||
|
||||||
|
(hide_inactive && el.style == GraphicElement::STYLE_INACTIVE))
|
||||||
|
continue;
|
||||||
|
switch (el.type) {
|
||||||
|
case GraphicElement::TYPE_LINE:
|
||||||
|
case GraphicElement::TYPE_ARROW:
|
||||||
|
out << stringf("<line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" stroke=\"%s\"/>", (el.x1 + dxy.x) * scale,
|
||||||
|
(el.y1 + dxy.y) * scale, (el.x2 + dxy.x) * scale, (el.y2 + dxy.y) * scale,
|
||||||
|
get_stroke_colour(el.style))
|
||||||
|
<< std::endl;
|
||||||
|
break;
|
||||||
|
case GraphicElement::TYPE_BOX:
|
||||||
|
out << stringf("<rect x=\"%f\" y=\"%f\" width=\"%f\" height=\"%f\" stroke=\"%s\" fill=\"%s\"/>",
|
||||||
|
(el.x1 + dxy.x) * scale, (el.y1 + dxy.y) * scale, (el.x2 - el.x1) * scale,
|
||||||
|
(el.y2 - el.y1) * scale, get_stroke_colour(el.style),
|
||||||
|
el.style == GraphicElement::STYLE_ACTIVE ? "#FF8080" : "none")
|
||||||
|
<< std::endl;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(const std::string &flags)
|
||||||
|
{
|
||||||
|
std::vector<std::string> options;
|
||||||
|
boost::algorithm::split(options, flags, boost::algorithm::is_space());
|
||||||
|
bool noroute = false;
|
||||||
|
for (const auto &opt : options) {
|
||||||
|
if (boost::algorithm::starts_with(opt, "scale=")) {
|
||||||
|
scale = float(std::stod(opt.substr(6)));
|
||||||
|
continue;
|
||||||
|
} else if (opt == "hide_routing") {
|
||||||
|
noroute = true;
|
||||||
|
} else if (opt == "hide_inactive") {
|
||||||
|
hide_inactive = true;
|
||||||
|
} else {
|
||||||
|
log_error("Unknown SVG option '%s'\n", opt.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float max_x = 0, max_y = 0;
|
||||||
|
for (auto group : ctx->getGroups()) {
|
||||||
|
auto decal = ctx->getGroupDecal(group);
|
||||||
|
for (auto el : ctx->getDecalGraphics(decal.decal)) {
|
||||||
|
max_x = std::max(max_x, decal.x + el.x1 + 1);
|
||||||
|
max_y = std::max(max_y, decal.y + el.y1 + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto bel : ctx->getBels()) {
|
||||||
|
auto decal = ctx->getBelDecal(bel);
|
||||||
|
for (auto el : ctx->getDecalGraphics(decal.decal)) {
|
||||||
|
max_x = std::max(max_x, decal.x + el.x1 + 1);
|
||||||
|
max_y = std::max(max_y, decal.y + el.y1 + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto wire : ctx->getWires()) {
|
||||||
|
auto decal = ctx->getWireDecal(wire);
|
||||||
|
for (auto el : ctx->getDecalGraphics(decal.decal)) {
|
||||||
|
max_x = std::max(max_x, decal.x + el.x1 + 1);
|
||||||
|
max_y = std::max(max_y, decal.y + el.y1 + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto pip : ctx->getPips()) {
|
||||||
|
auto decal = ctx->getPipDecal(pip);
|
||||||
|
for (auto el : ctx->getDecalGraphics(decal.decal)) {
|
||||||
|
max_x = std::max(max_x, decal.x + el.x1 + 1);
|
||||||
|
max_y = std::max(max_y, decal.y + el.y1 + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << std::endl;
|
||||||
|
out << stringf("<svg viewBox=\"0 0 %f %f\" width=\"%f\" height=\"%f\" xmlns=\"http://www.w3.org/2000/svg\">",
|
||||||
|
max_x * scale, max_y * scale, max_x * scale, max_y * scale)
|
||||||
|
<< std::endl;
|
||||||
|
out << "<rect x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" stroke=\"#fff\" fill=\"#fff\"/>" << std::endl;
|
||||||
|
for (auto group : ctx->getGroups())
|
||||||
|
write_decal(ctx->getGroupDecal(group));
|
||||||
|
for (auto bel : ctx->getBels())
|
||||||
|
write_decal(ctx->getBelDecal(bel));
|
||||||
|
if (!noroute) {
|
||||||
|
for (auto wire : ctx->getWires())
|
||||||
|
write_decal(ctx->getWireDecal(wire));
|
||||||
|
for (auto pip : ctx->getPips())
|
||||||
|
write_decal(ctx->getPipDecal(pip));
|
||||||
|
}
|
||||||
|
out << "</svg>" << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Context::writeSVG(const std::string &filename, const std::string &flags) const
|
||||||
|
{
|
||||||
|
std::ofstream out(filename);
|
||||||
|
SVGWriter(this, out)(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_END
|
1
python/svg_placement.py
Normal file
1
python/svg_placement.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
ctx.writeSVG(ctx.top_module + "_placed.svg", "scale=50 hide_routing")
|
1
python/svg_routing.py
Normal file
1
python/svg_routing.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
ctx.writeSVG(ctx.top_module + "_routed.svg", "scale=500")
|
1
python/svg_routing_fast.py
Normal file
1
python/svg_routing_fast.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
ctx.writeSVG(ctx.top_module + "_routed.svg", "scale=500 hide_inactive")
|
Loading…
Reference in New Issue
Block a user