From c854b6ba3bc4efb9baff699e91acafabfe08ebdb Mon Sep 17 00:00:00 2001 From: Cassie Jones Date: Fri, 26 Apr 2019 02:01:46 -0400 Subject: [PATCH] Implement yosys passes to synthesize to NaN gates --- .gitignore | 4 + Makefile | 13 +++ dff_nan.pmg | 19 +++++ erase_b2f.pmg | 8 ++ example/example.sv | 10 +++ example/example.ys | 4 + gateware.sv | 43 ++++++++++ nangate.cc | 202 +++++++++++++++++++++++++++++++++++++++++++++ share_nan.pmg | 9 ++ 9 files changed, 312 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 dff_nan.pmg create mode 100644 erase_b2f.pmg create mode 100644 example/example.sv create mode 100644 example/example.ys create mode 100644 gateware.sv create mode 100644 nangate.cc create mode 100644 share_nan.pmg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d061bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*_pm.h +*.d +*.so +*.so.dSYM diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..819b6dc --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +all: test + +test: example/example.sv example/example.ys nangate.so + yosys example/example.ys + +%_pm.h: %.pmg + pmgen $< $@ + +nangate.so: nangate.cc erase_b2f_pm.h dff_nan_pm.h share_nan_pm.h + yosys-config --build $@ $< + +clean: + rm -rf -- *.d *.so *.so.dSYM *_pm.h diff --git a/dff_nan.pmg b/dff_nan.pmg new file mode 100644 index 0000000..ac4cb8b --- /dev/null +++ b/dff_nan.pmg @@ -0,0 +1,19 @@ +match dff + // @TODO: Support more DFF types + select dff->type == $_DFF_P_ +endmatch + +match input + select input->type == \fp3_to_bit + index port(input, \Y) === port(dff, \D) + optional +endmatch + +match output + select output->type == \bit_to_fp3 + index port(output, \A) === port(dff, \Q) + optional +endmatch + +if (!input and !output) + reject; diff --git a/erase_b2f.pmg b/erase_b2f.pmg new file mode 100644 index 0000000..2a9acee --- /dev/null +++ b/erase_b2f.pmg @@ -0,0 +1,8 @@ +match base + select base->type == \fp3_to_bit +endmatch + +match target + select target->type == \bit_to_fp3 + index port(target, \A) === port(base, \Y) +endmatch diff --git a/example/example.sv b/example/example.sv new file mode 100644 index 0000000..250938d --- /dev/null +++ b/example/example.sv @@ -0,0 +1,10 @@ +module hello #(parameter WIDTH = 4, parameter MAX = 15) (input clock, reset, output max); + +logic [WIDTH-1:0] counter; +assign max = counter == MAX; +always @(posedge clock) begin + if (reset || max) counter <= 0; + else counter <= counter + 1; +end + +endmodule diff --git a/example/example.ys b/example/example.ys new file mode 100644 index 0000000..3de18f0 --- /dev/null +++ b/example/example.ys @@ -0,0 +1,4 @@ +plugin -i nangate.so +read_verilog -sv example/example.sv +synth_nan -top hello +synth_ice40 -noflatten -top hello diff --git a/gateware.sv b/gateware.sv new file mode 100644 index 0000000..bdcd6a1 --- /dev/null +++ b/gateware.sv @@ -0,0 +1,43 @@ +`define POS0 3'b000 +`define NEG0 3'b100 +`define POS1 3'b001 +`define NEG1 3'b101 +`define PINF 3'b010 +`define NINF 3'b110 +`define PNAN 3'b011 +`define NNAN 3'b111 +`define ANAN 3'b?11 +`define ANYF 3'b??? + +// A NaN gate! Computes `Inf - max(x+y, -Inf)`. +// See http://tom7.org/nand/ +module nan( + input [2:0] A, + input [2:0] B, output reg [2:0] Y +); + +always begin + casez ({A, B}) + {`ANYF, `ANAN}, {`ANAN, `ANYF}: Y = `PINF; + {`PINF, `NINF}, {`NINF, `PINF}: Y = `PINF; + {`ANYF, `PINF}, {`PINF, `ANYF}: Y = `PNAN; + default: Y = `PINF; + endcase +end +endmodule + +// Convert a bit into an fp3 +module bit_to_fp3(input A, output [2:0] Y); +assign Y = A ? `PNAN : `PINF; +endmodule + +// Convert an fp3 into a bit +module fp3_to_bit(input [2:0] A, output reg Y); +always begin + case (A) + `PNAN, `NNAN: Y = 1'b1; + default: Y = 1'b0; + endcase +end +endmodule + diff --git a/nangate.cc b/nangate.cc new file mode 100644 index 0000000..4aca3f4 --- /dev/null +++ b/nangate.cc @@ -0,0 +1,202 @@ +#include "kernel/yosys.h" +#include "kernel/celltypes.h" +#include "erase_b2f_pm.h" +#include "dff_nan_pm.h" +#include "share_nan_pm.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct NandToNaNWorker +{ + int nand_count = 0, not_count = 0, b2f_count = 0, f2b_count = 0; + RTLIL::Design *design; + RTLIL::Module *module; + + NandToNaNWorker(RTLIL::Design *design, RTLIL::Module *module) : + design(design), module(module) + { + for (auto cell : module->selected_cells()) { + if (cell->type == "$_NAND_") { + b2f_count += 2; + nand_count += 1; + f2b_count += 1; + RTLIL::Cell *b2fA = module->addCell(NEW_ID, "\\bit_to_fp3"); + RTLIL::Cell *b2fB = module->addCell(NEW_ID, "\\bit_to_fp3"); + RTLIL::Cell *nan = module->addCell(NEW_ID, "\\nan"); + RTLIL::Cell *f2b = module->addCell(NEW_ID, "\\fp3_to_bit"); + b2fA->setPort("\\A", cell->getPort("\\A")); + b2fA->setPort("\\Y", module->addWire(NEW_ID, 3)); + b2fB->setPort("\\A", cell->getPort("\\B")); + b2fB->setPort("\\Y", module->addWire(NEW_ID, 3)); + f2b->setPort("\\A", module->addWire(NEW_ID, 3)); + f2b->setPort("\\Y", cell->getPort("\\Y")); + nan->setPort("\\A", b2fA->getPort("\\Y")); + nan->setPort("\\B", b2fB->getPort("\\Y")); + nan->setPort("\\Y", f2b->getPort("\\A")); + module->swap_names(cell, nan); + module->remove(cell); + } else if (cell->type == "$_NOT_") { + b2f_count += 1; + not_count += 1; + f2b_count += 1; + RTLIL::Cell *b2f = module->addCell(NEW_ID, "\\bit_to_fp3"); + RTLIL::Cell *nan = module->addCell(NEW_ID, "\\nan"); + RTLIL::Cell *f2b = module->addCell(NEW_ID, "\\fp3_to_bit"); + b2f->setPort("\\A", cell->getPort("\\A")); + b2f->setPort("\\Y", module->addWire(NEW_ID, 3)); + f2b->setPort("\\A", module->addWire(NEW_ID, 3)); + f2b->setPort("\\Y", cell->getPort("\\Y")); + nan->setPort("\\A", b2f->getPort("\\Y")); + nan->setPort("\\B", b2f->getPort("\\Y")); + nan->setPort("\\Y", f2b->getPort("\\A")); + module->swap_names(cell, nan); + module->remove(cell); + } + } + } +}; + +struct NandToNaNPass : public Pass { + NandToNaNPass() : Pass("nand_to_nan") {} + void execute(vector args, Design *design) override { + log_header(design, "Executing NAND_TO_NaN pass (implementing tom7 logic)\n"); + log_push(); + Pass::call(design, "read_verilog -lib -sv gateware.sv"); + log_pop(); + + (void) args; + for (auto module : design->selected_modules()) { + log("Replacing NAND with NaN in module %s...\n", log_id(module)); + NandToNaNWorker worker(design, module); + log("Replaced %d NAND gates and %d NOT gates.\n", + worker.nand_count, worker.not_count); + log("Inserted:\n nan: %5d\n bit_to_fp3: %5d\n fp3_to_bit: %5d\n", + worker.nand_count + worker.not_count, + worker.b2f_count, worker.f2b_count); + } + } +} NandToNaNPass; + +struct DffToFp3Pass : public Pass { + DffToFp3Pass() : Pass("dff_nan") {} + void execute(vector args, Design *design) override { + log_header(design, "Executing DFF_NaN pass (widening flipflops to hold floats)\n"); + (void) args; + for (auto module : design->selected_modules()) { + log(" Module %s\n", log_id(module)); + dff_nan_pm pm(module, module->selected_cells()); + pool dffs; + pm.run([&]() { dffs.insert(pm.st.dff); }); + for (auto &dff : dffs) { + RTLIL::Cell *f2b = module->addCell(NEW_ID, "\\fp3_to_bit"); + f2b->setPort("\\A", module->addWire(NEW_ID, 3)); + f2b->setPort("\\Y", dff->getPort("\\Q")); + RTLIL::Cell *b2f = module->addCell(NEW_ID, "\\bit_to_fp3"); + b2f->setPort("\\A", dff->getPort("\\D")); + b2f->setPort("\\Y", module->addWire(NEW_ID, 3)); + for (int i = 0; i < 3; i++) { + // @TODO: Support more DFF types + assert(dff->type == "$_DFF_P_"); + RTLIL::Cell *new_ff = module->addCell(NEW_ID, "$_DFF_P_"); + new_ff->setPort("\\C", dff->getPort("\\C")); + new_ff->setPort("\\D", b2f->getPort("\\Y")[i]); + new_ff->setPort("\\Q", f2b->getPort("\\A")[i]); + } + module->remove(dff); + } + log("Converted %d flip-flops to hold floats\n", GetSize(dffs)); + } + } +} DffToFp3Pass; + +struct EraseFpBitPass : public Pass { + EraseFpBitPass() : Pass("simplify_nan") {} + void execute(vector args, Design *design) override { + log_header(design, "Executing SIMPLIFY_NaN pass (erasing useless conversion chains)\n"); + (void) args; + for (auto module : design->selected_modules()) { + log("Simplifying NaN conversions in module %s\n", log_id(module)); + erase_b2f_pm pm(module, module->selected_cells()); + pool eraseCells; + pm.run([&]() { + module->connect(pm.st.base->getPort("\\A"), pm.st.target->getPort("\\Y")); + eraseCells.insert(pm.st.target); + }); + for (auto cell : eraseCells) { + module->remove(cell); + } + log("Removed %d bit_to_fp3 nodes\n", GetSize(eraseCells)); + } + } +} EraseFpBitPass; + +struct ShareNaN : public Pass { + ShareNaN() : Pass("share_nan") {} + void execute(vector args, Design *design) override { + log_header(design, "Executing SHARE_NAN pass (merging conversion cells).\n"); + (void) args; + for (auto module : design->selected_modules()) { + log("Module %s\n", log_id(module)); + share_nan_pm pm(module, module->selected_cells()); + mfp sharedCells; + pm.run([&]() { sharedCells.merge(pm.st.cvt_a, pm.st.cvt_b); }); + int merged = 0; + for (auto &entry : sharedCells) { + auto &main = sharedCells.find(entry); + if (entry == main) continue; + merged += 1; + module->connect(main->getPort("\\Y"), entry->getPort("\\Y")); + module->remove(entry); + } + log("Merged %d conversion cells\n", merged); + } + } +} ShareNaNPass; + +struct TechmapNaN : public Pass { + TechmapNaN() : Pass("techmap_nan", "techmap NaN gates") {} + void execute(vector, Design *design) override { + Pass::call(design, "techmap -autoproc -extern -map gateware.sv"); + } +} TechmapNaNPass; + +struct SynthNaN : public Pass { + SynthNaN() : Pass("synth_nan", "synthesize to tom7 logic") {} + void help() override { + log("synth_nan [options]\n\n"); + log("Runs the equivalent of the following script:\n\n"); + log(" synth [-top ]\n"); + log(" abc -g NAND\n"); + log(" nand_to_nan\n"); + log(" share_nan\n"); + log(" dff_nan\n"); + log(" nan_simplify\n"); + log(" clean\n"); + } + void execute(vector args, Design *design) override { + string synth_args; + log_header(design, "Executing SYNTH_NaN pass (synthesizing to tom7 logic).\n"); + log_push(); + for (size_t i = 0; i < args.size(); i++) { + if (args[i] == "-top") { + synth_args += " -top "; + synth_args += args[i+1]; + i += 1; + continue; + } + } + Pass::call(design, "synth" + synth_args); + Pass::call(design, "abc -g NAND"); + Pass::call(design, "nand_to_nan"); + Pass::call(design, "share_nan"); + Pass::call(design, "dff_nan"); + Pass::call(design, "simplify_nan"); + Pass::call(design, "clean"); + Pass::call(design, "techmap_nan"); + log_pop(); + } +} SynthNaNPass; + +PRIVATE_NAMESPACE_END + diff --git a/share_nan.pmg b/share_nan.pmg new file mode 100644 index 0000000..571f3b8 --- /dev/null +++ b/share_nan.pmg @@ -0,0 +1,9 @@ +match cvt_a + select cvt_a->type.in(\fp3_to_bit, \bit_to_fp3) +endmatch + +match cvt_b + select cvt_b->type.in(\fp3_to_bit, \bit_to_fp3) + index cvt_b->type === cvt_a->type + index port(cvt_b, \A) === port(cvt_a, \A) +endmatch -- 2.43.2