commit 0d2faab2f0211afadd26a9350ed5aa2b1ba3c0d1
parent ac8796a476323808a396dddd700772bcab5f504c
Author: yash modi <yash3108m@gmail.com>
Date: Sat, 28 Mar 2026 08:09:13 +0530
Week 2: Begin Verilog Emitter Implementation (Part 2: Core logic)
Diffstat:
| M | src/emitter.cpp | | | 176 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 176 insertions(+), 0 deletions(-)
diff --git a/src/emitter.cpp b/src/emitter.cpp
@@ -130,3 +130,179 @@ void Emitter::emitNFAModule(const NFA &nfa, const std::filesystem::path &outputD
}
// Magnitude comparators for contiguous wildcard ranges
+ for (auto &[srcLocal, chars] : charsBySrc)
+ {
+ std::sort(chars.begin(), chars.end());
+ size_t i = 0;
+ while (i < chars.size())
+ {
+ size_t j = i;
+ while (j + 1 < chars.size() && chars[j + 1] == chars[j] + 1)
+ {
+ j++;
+ }
+ if (j - i >= 3)
+ {
+ terms.push_back("(state_reg[" + std::to_string(srcLocal) +
+ "] && (char_in >= 8'd" + std::to_string(chars[i]) +
+ ") && (char_in <= 8'd" + std::to_string(chars[j]) + "))");
+ }
+ else
+ {
+ for (size_t k = i; k <= j; ++k)
+ {
+ terms.push_back("(state_reg[" + std::to_string(srcLocal) +
+ "] && (char_in == 8'd" + std::to_string(chars[k]) + "))");
+ }
+ }
+ i = j + 1;
+ }
+ }
+ }
+
+ if (terms.empty())
+ {
+ out << "1'b0;\n";
+ }
+ else
+ {
+ out << terms[0];
+ for (size_t t = 1; t < terms.size(); ++t)
+ {
+ out << " | " << terms[t];
+ }
+ out << ";\n";
+ }
+ }
+
+ out << "\n always @(posedge clk) begin\n"
+ << " if (rst || start) begin\n"
+ << " // Reset to start state (one-hot)\n"
+ << " state_reg <= 1 << " << globalToLocal.at(nfa.startStateId) << ";\n"
+ << " end else if (en) begin\n"
+ << " state_reg <= next_state;\n"
+ << " end\n"
+ << " end\n\n"
+ << " // Match logic: asserted on cycle following end_of_str\n"
+ << " always @(posedge clk) begin\n"
+ << " if (rst || start) begin\n"
+ << " match <= 1'b0;\n"
+ << " end else if (en) begin\n"
+ << " if (end_of_str) begin\n"
+ << " match <= ";
+
+ std::vector<std::string> acceptTerms;
+ for (const auto &[id, state] : nfa.states)
+ {
+ if (state.isAccept)
+ {
+ acceptTerms.push_back("state_reg[" + std::to_string(globalToLocal.at(id)) + "]");
+ }
+ }
+
+ if (acceptTerms.empty())
+ {
+ out << "1'b0";
+ }
+ else if (acceptTerms.size() == 1)
+ {
+ out << acceptTerms[0];
+ }
+ else
+ {
+ out << "(|{";
+ for (size_t t = 0; t < acceptTerms.size(); ++t)
+ {
+ out << acceptTerms[t] << (t < acceptTerms.size() - 1 ? ", " : "");
+ }
+ out << "})";
+ }
+
+ out << ";\n"
+ << " end else begin\n"
+ << " match <= 1'b0;\n"
+ << " end\n"
+ << " end\n"
+ << " end\n\n"
+ << "endmodule\n";
+}
+
+// =============================================================================
+// emitTopModule — top.v wrapper
+// =============================================================================
+void Emitter::emitTopModule(const std::vector<std::unique_ptr<NFA>> &nfas, const std::filesystem::path &outputDir)
+{
+ auto filePath = outputDir / "top.v";
+ std::ofstream out;
+ out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
+
+ try
+ {
+ out.open(filePath);
+ }
+ catch (const std::ios_base::failure &e)
+ {
+ throw std::system_error(errno, std::generic_category(), "Could not open " + filePath.string() + " for writing");
+ }
+
+ out << "`timescale 1ns / 1ps\n\n";
+ out << "module top (\n"
+ << " input wire clk,\n"
+ << " input wire en,\n"
+ << " input wire rst,\n"
+ << " input wire start,\n"
+ << " input wire end_of_str,\n"
+ << " input wire [7:0] char_in,\n"
+ << " output wire [" << (nfas.size() - 1) << ":0] match_bus\n"
+ << ");\n\n";
+
+ for (size_t i = 0; i < nfas.size(); ++i)
+ {
+ out << " nfa_" << nfas[i]->regexIndex << " inst_" << nfas[i]->regexIndex << " (\n"
+ << " .clk(clk), .en(en), .rst(rst), .start(start), .end_of_str(end_of_str), .char_in(char_in), .match(match_bus[" << i << "])\n"
+ << " );\n\n";
+ }
+ out << "endmodule\n";
+}
+
+// =============================================================================
+// emitTestbench — tb_top.v
+// =============================================================================
+void Emitter::emitTestbench(const std::vector<std::unique_ptr<NFA>> &nfas,
+ const std::filesystem::path &outputDir,
+ const std::vector<std::string> &testStrings,
+ const std::vector<std::string> &expectedMatches)
+{
+ auto filePath = outputDir / "tb_top.v";
+ std::ofstream out;
+ out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
+
+ try
+ {
+ out.open(filePath);
+ }
+ catch (const std::ios_base::failure &e)
+ {
+ throw std::system_error(errno, std::generic_category(), "Could not open " + filePath.string() + " for writing");
+ }
+
+ size_t numNFAs = nfas.size();
+
+ out << "`timescale 1ns / 1ps\n\n"
+ << "module tb_top;\n"
+ << " reg clk, en, rst, start, end_of_str;\n"
+ << " reg [7:0] char_in;\n"
+ << " wire [" << (numNFAs > 0 ? numNFAs - 1 : 0) << ":0] match_bus;\n\n"
+ << " top uut (.clk(clk), .en(en), .rst(rst), .start(start), .end_of_str(end_of_str), .char_in(char_in), .match_bus(match_bus));\n\n"
+ << " always #5 clk = ~clk;\n\n"
+ << " initial begin\n"
+ << " // synthesis translate_off\n"
+ << " `ifndef SYNTHESIS\n"
+ << " $dumpfile(\"dump.vcd\");\n"
+ << " $dumpvars(0, tb_top);\n"
+ << " `endif\n"
+ << " // synthesis translate_on\n\n"
+ << " clk = 0; en = 1; rst = 1; start = 0; end_of_str = 0; char_in = 0;\n"
+ << " #20 rst = 0; #10;\n\n";
+
+ for (size_t i = 0; i < testStrings.size(); ++i)