xiiregexbuilder
FPGA-Accelerated Regular Expression Matching Enginesrc/emitter.cpp (46608B)
1#include "emitter.h"
2#include <fstream>
3#include <iostream>
4#include <iomanip>
5#include <sstream>
6#include <algorithm>
7#include <set>
8#include <map>
9#include <system_error>
10#include <filesystem>
11
12// =============================================================================
13// Emitter::emit - orchestration
14// =============================================================================
15void Emitter::emit(const std::vector<std::unique_ptr<NFA>> &nfas,
16 const std::string &outputDirStr,
17 const std::vector<std::string> &testStrings,
18 const std::vector<std::string> &expectedMatches,
19 bool isPII)
20{
21 if (nfas.empty())
22 {
23 std::cout << "No valid NFAs were provided; skipping Verilog emission." << std::endl;
24 return;
25 }
26
27 std::filesystem::path outputDir(outputDirStr);
28 try
29 {
30 std::filesystem::create_directories(outputDir);
31 }
32 catch (const std::filesystem::filesystem_error &e)
33 {
34 throw std::system_error(e.code(), "Failed to create output directory: " + outputDir.string());
35 }
36
37 for (const auto &nfa : nfas)
38 {
39 emitNFAModule(*nfa, outputDir, isPII);
40 }
41
42 emitTopModule(nfas, outputDir);
43 if (isPII)
44 {
45 emitPIIUART(outputDir);
46 emitPIIUARTTx(outputDir);
47 emitPIITopFPGA(nfas, outputDir);
48 }
49 else
50 {
51 emitUARTRX(outputDir);
52 emitUARTTX(outputDir);
53 emitFIFO(outputDir);
54 emitTopFPGA(nfas, outputDir);
55 }
56
57 emitConstraints(nfas, outputDir);
58 emitTestbench(nfas, outputDir, testStrings, expectedMatches);
59}
60
61// =============================================================================
62// emitNFAModule - one file per regex
63// =============================================================================
64void Emitter::emitNFAModule(const NFA &nfa, const std::filesystem::path &outputDir, bool isPII)
65{
66 auto filePath = outputDir / ("nfa_" + std::to_string(nfa.regexIndex) + ".v");
67 std::ofstream out;
68 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
69
70 try
71 {
72 out.open(filePath);
73 }
74 catch (const std::ios_base::failure &e)
75 {
76 throw std::system_error(errno, std::generic_category(), "Could not open " + filePath.string() + " for writing");
77 }
78
79 int numStates = static_cast<int>(nfa.states.size());
80 if (numStates == 0)
81 return;
82
83 std::map<int, int> globalToLocal;
84 int localIdx = 0;
85
86 // Ensure start state is always local state 0
87 globalToLocal[nfa.startStateId] = localIdx++;
88
89 // Sort other state IDs for deterministic output
90 std::set<int> otherIds;
91 for (const auto &pair : nfa.states)
92 {
93 if (pair.first != nfa.startStateId)
94 otherIds.insert(pair.first);
95 }
96 for (int id : otherIds)
97 {
98 globalToLocal[id] = localIdx++;
99 }
100
101 out << "`timescale 1ns / 1ps\n\n";
102 out << "// NFA for regex index " << nfa.regexIndex << "\n";
103 out << "module nfa_" << nfa.regexIndex << " (\n"
104 << " input wire clk,\n"
105 << " input wire en,\n"
106 << " input wire rst,\n"
107 << " input wire start,\n"
108 << " input wire end_of_str,\n"
109 << " input wire [7:0] char_in,\n"
110 << " output " << (isPII ? "wire " : "reg ") << "match,\n"
111 << " output wire active\n"
112 << ");\n\n";
113
114 out << " // One-hot state register\n"
115 << " reg [" << numStates - 1 << ":0] state_reg;\n"
116 << " wire [" << numStates - 1 << ":0] next_state;\n\n";
117
118 std::map<int, std::vector<std::pair<int, unsigned char>>> invertedTransitions;
119 for (const auto &[srcGlobalId, srcState] : nfa.states)
120 {
121 for (const auto &[c, dstIds] : srcState.transitions)
122 {
123 for (int dstGlobalId : dstIds)
124 {
125 invertedTransitions[dstGlobalId].push_back({srcGlobalId, c});
126 }
127 }
128 }
129
130 for (const auto &[globalId, localId] : globalToLocal)
131 {
132 out << " assign next_state[" << localId << "] = ";
133
134 std::vector<std::string> terms;
135
136 if (isPII && localId == 0)
137 {
138 terms.push_back("1'b1");
139 }
140
141 if (auto it = invertedTransitions.find(globalId); it != invertedTransitions.end())
142 {
143 std::map<int, std::vector<unsigned char>> charsBySrc;
144 for (const auto &[srcGlobalId, c] : it->second)
145 {
146 charsBySrc[globalToLocal.at(srcGlobalId)].push_back(c);
147 }
148
149 // Magnitude comparators for contiguous wildcard ranges
150 for (auto &[srcLocal, chars] : charsBySrc)
151 {
152 std::sort(chars.begin(), chars.end());
153 size_t i = 0;
154 while (i < chars.size())
155 {
156 size_t j = i;
157 while (j + 1 < chars.size() && chars[j + 1] == chars[j] + 1)
158 {
159 j++;
160 }
161 if (j - i >= 3)
162 {
163 terms.push_back("(state_reg[" + std::to_string(srcLocal) +
164 "] && (char_in >= 8'd" + std::to_string(chars[i]) +
165 ") && (char_in <= 8'd" + std::to_string(chars[j]) + "))");
166 }
167 else
168 {
169 for (size_t k = i; k <= j; ++k)
170 {
171 terms.push_back("(state_reg[" + std::to_string(srcLocal) +
172 "] && (char_in == 8'd" + std::to_string(chars[k]) + "))");
173 }
174 }
175 i = j + 1;
176 }
177 }
178 }
179
180 if (terms.empty())
181 {
182 out << "1'b0;\n";
183 }
184 else
185 {
186 out << terms[0];
187 for (size_t t = 1; t < terms.size(); ++t)
188 {
189 out << " | " << terms[t];
190 }
191 out << ";\n";
192 }
193 }
194
195 out << "\n always @(posedge clk) begin\n"
196 << " if (rst || start) begin\n"
197 << " // Reset to start state (one-hot)\n"
198 << " state_reg <= 1 << " << globalToLocal.at(nfa.startStateId) << ";\n"
199 << " end else if (en) begin\n"
200 << " state_reg <= next_state;\n"
201 << " end\n"
202 << " end\n\n";
203
204 if (isPII)
205 {
206 out << " // Match logic: asserted immediately on accept state (combinational)\n"
207 << " assign match = ";
208 }
209 else
210 {
211 out << " // Match logic: asserted on cycle following end_of_str\n"
212 << " always @(posedge clk) begin\n"
213 << " if (rst || start) begin\n"
214 << " match <= 1'b0;\n"
215 << " end else if (en) begin\n"
216 << " if (end_of_str) begin\n"
217 << " match <= ";
218 }
219
220 std::vector<std::string> acceptTerms;
221 for (const auto &[id, state] : nfa.states)
222 {
223 if (state.isAccept)
224 {
225 acceptTerms.push_back("state_reg[" + std::to_string(globalToLocal.at(id)) + "]");
226 }
227 }
228
229 if (acceptTerms.empty())
230 {
231 out << "1'b0";
232 }
233 else if (acceptTerms.size() == 1)
234 {
235 out << acceptTerms[0];
236 }
237 else
238 {
239 out << "(|{";
240 for (size_t t = 0; t < acceptTerms.size(); ++t)
241 {
242 out << acceptTerms[t] << (t < acceptTerms.size() - 1 ? ", " : "");
243 }
244 out << "})";
245 }
246
247 if (isPII)
248 {
249 out << ";\n\n";
250 }
251 else
252 {
253 out << ";\n"
254 << " end else begin\n"
255 << " match <= 1'b0;\n"
256 << " end\n"
257 << " end\n"
258 << " end\n\n";
259 }
260 out << " // Active logic: high if any state other than state 0 is active\n";
261 if (numStates > 1)
262 {
263 out << " assign active = |state_reg[" << numStates - 1 << ":1];\n\n";
264 }
265 else
266 {
267 out << " assign active = 1'b0;\n\n";
268 }
269 out << "endmodule\n";
270}
271
272// =============================================================================
273// emitTopModule - top.v wrapper
274// =============================================================================
275void Emitter::emitTopModule(const std::vector<std::unique_ptr<NFA>> &nfas, const std::filesystem::path &outputDir)
276{
277 auto filePath = outputDir / "top.v";
278 std::ofstream out;
279 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
280
281 try
282 {
283 out.open(filePath);
284 }
285 catch (const std::ios_base::failure &e)
286 {
287 throw std::system_error(errno, std::generic_category(), "Could not open " + filePath.string() + " for writing");
288 }
289
290 out << "`timescale 1ns / 1ps\n\n";
291 out << "module top (\n"
292 << " input wire clk,\n"
293 << " input wire en,\n"
294 << " input wire rst,\n"
295 << " input wire start,\n"
296 << " input wire end_of_str,\n"
297 << " input wire [7:0] char_in,\n"
298 << " output wire [" << (nfas.size() > 0 ? nfas.size() - 1 : 0) << ":0] match_bus,\n"
299 << " output wire [" << (nfas.size() > 0 ? nfas.size() - 1 : 0) << ":0] active_bus\n"
300 << ");\n\n";
301
302 for (size_t i = 0; i < nfas.size(); ++i)
303 {
304 out << " nfa_" << nfas[i]->regexIndex << " inst_" << nfas[i]->regexIndex << " (\n"
305 << " .clk(clk), .en(en), .rst(rst), .start(start), .end_of_str(end_of_str), .char_in(char_in), .match(match_bus[" << i << "]), .active(active_bus[" << i << "])\n"
306 << " );\n\n";
307 }
308 out << "endmodule\n";
309}
310
311// =============================================================================
312// emitTestbench - tb_top.v
313// =============================================================================
314void Emitter::emitTestbench(const std::vector<std::unique_ptr<NFA>> &nfas,
315 const std::filesystem::path &outputDir,
316 const std::vector<std::string> &testStrings,
317 const std::vector<std::string> &expectedMatches)
318{
319 auto filePath = outputDir / "tb_top.v";
320 std::ofstream out;
321 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
322
323 try
324 {
325 out.open(filePath);
326 }
327 catch (const std::ios_base::failure &e)
328 {
329 throw std::system_error(errno, std::generic_category(), "Could not open " + filePath.string() + " for writing");
330 }
331
332 size_t numNFAs = nfas.size();
333
334 out << "`timescale 1ns / 1ps\n\n"
335 << "module tb_top;\n"
336 << " reg clk, en, rst, start, end_of_str;\n"
337 << " reg [7:0] char_in;\n"
338 << " wire [" << (numNFAs > 0 ? numNFAs - 1 : 0) << ":0] match_bus;\n"
339 << " wire [" << (numNFAs > 0 ? numNFAs - 1 : 0) << ":0] active_bus;\n\n"
340 << " top uut (.clk(clk), .en(en), .rst(rst), .start(start), .end_of_str(end_of_str), .char_in(char_in), .match_bus(match_bus), .active_bus(active_bus));\n\n"
341 << " always #5 clk = ~clk;\n\n"
342 << " initial begin\n"
343 << " // synthesis translate_off\n"
344 << " `ifndef SYNTHESIS\n"
345 << " $dumpfile(\"dump.vcd\");\n"
346 << " $dumpvars(0, tb_top);\n"
347 << " `endif\n"
348 << " // synthesis translate_on\n\n"
349 << " clk = 0; en = 1; rst = 1; start = 0; end_of_str = 0; char_in = 0;\n"
350 << " #20 rst = 0; #10;\n\n";
351
352 for (size_t i = 0; i < testStrings.size(); ++i)
353 {
354 const std::string &s = testStrings[i];
355 out << " // Test case " << i << ": \"" << s << "\"\n"
356 << " start = 1; #10 start = 0;\n";
357
358 for (char c : s)
359 {
360 out << " char_in = 8'h" << std::hex << std::setw(2) << std::setfill('0')
361 << static_cast<int>(static_cast<unsigned char>(c)) << std::dec << "; #10;\n";
362 }
363
364 out << " end_of_str = 1; #10;\n";
365 out << " // Match output is valid on the cycle immediately following end_of_str assertion\n";
366
367 if (i < expectedMatches.size())
368 {
369 if (expectedMatches[i].length() != numNFAs)
370 {
371 throw std::runtime_error(
372 "Testbench generation error: expected_matches[" + std::to_string(i) +
373 "] has length " + std::to_string(expectedMatches[i].length()) +
374 " but " + std::to_string(numNFAs) + " NFAs exist.");
375 }
376 out << " if (match_bus === " << numNFAs << "'b" << expectedMatches[i] << ") begin\n"
377 << " $display(\"PASS: Test case " << i << " ('" << s << "') matches expected mask " << expectedMatches[i] << "\");\n"
378 << " end else begin\n"
379 << " $display(\"FAIL: Test case " << i << " ('" << s << "') expected " << expectedMatches[i] << ", got %b\", match_bus);\n"
380 << " end\n";
381 }
382 else
383 {
384 out << " $display(\"INFO: Result for '" << s << "': %b\", match_bus);\n";
385 }
386 out << " end_of_str = 0; #10;\n\n";
387 }
388
389 out << " $display(\"All tests completed.\");\n"
390 << " #100; $finish;\n end\nendmodule\n";
391}
392
393// =============================================================================
394// emitUARTRX - uart_rx.v
395// =============================================================================
396void Emitter::emitUARTRX(const std::filesystem::path &outputDir)
397{
398 auto filePath = outputDir / "uart_rx.v";
399 std::ofstream out(filePath);
400 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
401
402 out << "`timescale 1ns / 1ps\n\n";
403 out << R"(
404module uart_rx #(
405 parameter CLKS_PER_BIT = 868 // 100 MHz / 115200 Baud
406)(
407 input wire clk,
408 input wire rx,
409 output reg [7:0] rx_data,
410 output reg rx_ready
411);
412 localparam IDLE = 2'b00, START_BIT = 2'b01, DATA_BITS = 2'b10, STOP_BIT = 2'b11;
413 reg [1:0] state = IDLE;
414 reg [9:0] clk_count = 0;
415 reg [2:0] bit_idx = 0;
416
417 always @(posedge clk) begin
418 case (state)
419 IDLE: begin
420 rx_ready <= 1'b0;
421 clk_count <= 0;
422 bit_idx <= 0;
423 if (rx == 1'b0) state <= START_BIT;
424 end
425 START_BIT: begin
426 if (clk_count == (CLKS_PER_BIT-1)/2) begin
427 if (rx == 1'b0) begin
428 clk_count <= 0;
429 state <= DATA_BITS;
430 end else state <= IDLE;
431 end else clk_count <= clk_count + 1;
432 end
433 DATA_BITS: begin
434 if (clk_count < CLKS_PER_BIT-1) begin
435 clk_count <= clk_count + 1;
436 end else begin
437 clk_count <= 0;
438 rx_data[bit_idx] <= rx;
439 if (bit_idx < 7) bit_idx <= bit_idx + 1;
440 else state <= STOP_BIT;
441 end
442 end
443 STOP_BIT: begin
444 if (clk_count < CLKS_PER_BIT-1) begin
445 clk_count <= clk_count + 1;
446 end else begin
447 if (rx == 1'b1) rx_ready <= 1'b1;
448 state <= IDLE;
449 end
450 end
451 endcase
452 end
453endmodule
454)";
455}
456
457// =============================================================================
458// emitUARTTX - uart_tx.v
459// =============================================================================
460void Emitter::emitUARTTX(const std::filesystem::path &outputDir)
461{
462 auto filePath = outputDir / "uart_tx.v";
463 std::ofstream out(filePath);
464 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
465
466 out << "`timescale 1ns / 1ps\n\n";
467 out << R"(
468module uart_tx #(
469 parameter CLKS_PER_BIT = 868 // 100 MHz / 115200 baud
470)(
471 input wire clk,
472 input wire rst,
473 input wire [7:0] tx_data,
474 input wire tx_start,
475 output reg tx_busy,
476 output reg tx
477);
478 localparam S_IDLE = 3'd0;
479 localparam S_START_BIT = 3'd1;
480 localparam S_DATA_BITS = 3'd2;
481 localparam S_STOP_BIT = 3'd3;
482
483 reg [2:0] state = S_IDLE;
484 reg [9:0] clk_cnt = 10'd0;
485 reg [2:0] bit_idx = 3'd0;
486 reg [7:0] tx_shift = 8'd0;
487
488 always @(posedge clk) begin
489 if (rst) begin
490 state <= S_IDLE;
491 tx <= 1'b1;
492 tx_busy <= 1'b0;
493 clk_cnt <= 10'd0;
494 bit_idx <= 3'd0;
495 tx_shift <= 8'd0;
496 end else begin
497 case (state)
498 S_IDLE: begin
499 tx <= 1'b1;
500 tx_busy <= 1'b0;
501 clk_cnt <= 10'd0;
502 bit_idx <= 3'd0;
503 if (tx_start) begin
504 tx_shift <= tx_data;
505 tx_busy <= 1'b1;
506 state <= S_START_BIT;
507 end
508 end
509 S_START_BIT: begin
510 tx <= 1'b0;
511 if (clk_cnt < CLKS_PER_BIT - 1)
512 clk_cnt <= clk_cnt + 1;
513 else begin
514 clk_cnt <= 10'd0;
515 state <= S_DATA_BITS;
516 end
517 end
518 S_DATA_BITS: begin
519 tx <= tx_shift[0];
520 if (clk_cnt < CLKS_PER_BIT - 1)
521 clk_cnt <= clk_cnt + 1;
522 else begin
523 clk_cnt <= 10'd0;
524 tx_shift <= tx_shift >> 1;
525 if (bit_idx < 7)
526 bit_idx <= bit_idx + 1;
527 else begin
528 bit_idx <= 3'd0;
529 state <= S_STOP_BIT;
530 end
531 end
532 end
533 S_STOP_BIT: begin
534 tx <= 1'b1;
535 if (clk_cnt < CLKS_PER_BIT - 1)
536 clk_cnt <= clk_cnt + 1;
537 else begin
538 clk_cnt <= 10'd0;
539 state <= S_IDLE;
540 end
541 end
542 default: state <= S_IDLE;
543 endcase
544 end
545 end
546endmodule
547)";
548}
549
550// =============================================================================
551// emitFIFO - uart_rx_fifo.v
552// =============================================================================
553void Emitter::emitFIFO(const std::filesystem::path &outputDir)
554{
555 auto filePath = outputDir / "uart_rx_fifo.v";
556 std::ofstream out(filePath);
557 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
558
559 out << "`timescale 1ns / 1ps\n\n";
560 out << R"(
561module uart_rx_fifo #(
562 parameter DEPTH_LOG2 = 4 // depth = 2^DEPTH_LOG2 = 16
563)(
564 input wire clk,
565 input wire rst,
566 input wire [7:0] wr_data,
567 input wire wr_en,
568 output wire full,
569 output wire [7:0] rd_data,
570 input wire rd_en,
571 output wire empty
572);
573 localparam DEPTH = 1 << DEPTH_LOG2;
574
575 reg [7:0] mem [0:DEPTH-1];
576 reg [DEPTH_LOG2-1:0] wr_ptr = {DEPTH_LOG2{1'b0}};
577 reg [DEPTH_LOG2-1:0] rd_ptr = {DEPTH_LOG2{1'b0}};
578 reg [DEPTH_LOG2 :0] count = {(DEPTH_LOG2+1){1'b0}};
579
580 assign full = (count == DEPTH[DEPTH_LOG2:0]);
581 assign empty = (count == {(DEPTH_LOG2+1){1'b0}});
582 assign rd_data = mem[rd_ptr];
583
584 always @(posedge clk) begin
585 if (rst) begin
586 wr_ptr <= {DEPTH_LOG2{1'b0}};
587 rd_ptr <= {DEPTH_LOG2{1'b0}};
588 count <= {(DEPTH_LOG2+1){1'b0}};
589 end else begin
590 if (wr_en && !full && rd_en && !empty) begin
591 mem[wr_ptr] <= wr_data;
592 wr_ptr <= wr_ptr + 1;
593 rd_ptr <= rd_ptr + 1;
594 end else if (wr_en && !full) begin
595 mem[wr_ptr] <= wr_data;
596 wr_ptr <= wr_ptr + 1;
597 count <= count + 1;
598 end else if (rd_en && !empty) begin
599 rd_ptr <= rd_ptr + 1;
600 count <= count - 1;
601 end
602 end
603 end
604endmodule
605)";
606}
607
608// =============================================================================
609// emitTopFPGA - top_fpga.v
610// =============================================================================
611void Emitter::emitTopFPGA(const std::vector<std::unique_ptr<NFA>> &nfas, const std::filesystem::path &outputDir)
612{
613 auto filePath = outputDir / "top_fpga.v";
614 std::ofstream out(filePath);
615 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
616
617 const size_t numNFAs = nfas.size();
618 const std::string busHigh = std::to_string(numNFAs > 0 ? numNFAs - 1 : 0);
619
620 // File Header
621 out << "`timescale 1ns / 1ps\n\n"
622 << "// =============================================================================\n"
623 << "// top_fpga.v - FPGA Top-Level\n"
624 << "// Regex count: " << numNFAs << "\n"
625 << "//\n"
626 << "// Architecture:\n"
627 << "// uart_rx → uart_rx_fifo → Control FSM → top (NFA engine)\n"
628 << "// → uart_tx → host PC\n"
629 << "//\n"
630 << "// UART response packet (one line per newline received from host):\n"
631 << "// \"MATCH=<" << numNFAs << "-bit binary> BYTES=<8 hex> HITS=<4 hex per regex,comma-sep>\\r\\n\"\n"
632 << "//\n"
633 << "// Send '?' (0x3F) to query counters without feeding the NFA.\n"
634 << "// =============================================================================\n\n";
635
636 // Module Declaration
637 out << "module top_fpga #(\n"
638 << " parameter NUM_REGEX = " << numNFAs << ",\n"
639 << " parameter CLKS_PER_BIT = 868 // 100 MHz / 115200 baud\n"
640 << ")(\n"
641 << " input wire clk,\n"
642 << " input wire rst_btn,\n"
643 << " input wire uart_rx_pin,\n"
644 << " output wire uart_tx_pin,\n"
645 << " output reg [" << busHigh << ":0] match_leds\n"
646 << ");\n\n";
647
648 // uart_rx
649 out << " // UART RX\n"
650 << " wire [7:0] rx_data;\n"
651 << " wire rx_ready;\n\n"
652 << " uart_rx #(.CLKS_PER_BIT(CLKS_PER_BIT)) uart_rx_inst (\n"
653 << " .clk (clk),\n"
654 << " .rx (uart_rx_pin),\n"
655 << " .rx_data (rx_data),\n"
656 << " .rx_ready(rx_ready)\n"
657 << " );\n\n";
658
659 // FIFO
660 out << " // Input FIFO\n"
661 << " wire [7:0] fifo_rd_data;\n"
662 << " wire fifo_empty;\n"
663 << " wire fifo_full;\n"
664 << " reg fifo_rd_en = 1'b0;\n\n"
665 << " uart_rx_fifo #(.DEPTH_LOG2(4)) rx_fifo (\n"
666 << " .clk (clk),\n"
667 << " .rst (rst_btn),\n"
668 << " .wr_data(rx_data),\n"
669 << " .wr_en (rx_ready && !fifo_full),\n"
670 << " .full (fifo_full),\n"
671 << " .rd_data(fifo_rd_data),\n"
672 << " .rd_en (fifo_rd_en),\n"
673 << " .empty (fifo_empty)\n"
674 << " );\n\n";
675
676 // NFA engine
677 out << " // NFA Engine\n"
678 << " reg nfa_start = 1'b1;\n"
679 << " reg nfa_end_of_str = 1'b0;\n"
680 << " reg [7:0] nfa_char_in = 8'h00;\n"
681 << " reg nfa_en = 1'b0;\n"
682 << " wire [" << busHigh << ":0] match_bus;\n\n"
683 << " top regex_engine (\n"
684 << " .clk (clk),\n"
685 << " .en (nfa_en),\n"
686 << " .rst (rst_btn),\n"
687 << " .start (nfa_start),\n"
688 << " .end_of_str (nfa_end_of_str),\n"
689 << " .char_in (nfa_char_in),\n"
690 << " .match_bus (match_bus)\n"
691 << " );\n\n";
692
693 // Hardware Counters
694 out << " // Hardware Counters\n"
695 << " reg [31:0] byte_count = 32'd0;\n"
696 << " // One 16-bit hit counter per regex\n"
697 << " reg [15:0] match_count [0:15];\n"
698 << " integer ci;\n"
699 << " initial begin\n"
700 << " for (ci = 0; ci < 16; ci = ci + 1)\n"
701 << " match_count[ci] = 16'd0;\n"
702 << " end\n\n";
703
704 // uart_tx + TX drain sub-FSM
705 out << " // UART TX & Drain Sub-FSM\n"
706 << " localparam TX_BUF_LEN = 128;\n"
707 << " reg [7:0] tx_buf [0:TX_BUF_LEN-1];\n"
708 << " reg [6:0] tx_len = 7'd0;\n"
709 << " reg [6:0] tx_ptr = 7'd0;\n"
710 << " reg tx_send = 1'b0; // pulse: start draining tx_buf\n\n"
711 << " wire tx_busy;\n"
712 << " reg tx_start_r = 1'b0;\n"
713 << " reg [7:0] tx_data_r = 8'd0;\n\n"
714 << " uart_tx #(.CLKS_PER_BIT(CLKS_PER_BIT)) uart_tx_inst (\n"
715 << " .clk (clk),\n"
716 << " .rst (rst_btn),\n"
717 << " .tx_data (tx_data_r),\n"
718 << " .tx_start(tx_start_r),\n"
719 << " .tx_busy (tx_busy),\n"
720 << " .tx (uart_tx_pin)\n"
721 << " );\n\n"
722 << " // hex nibble → ASCII character\n"
723 << " function [7:0] hex_char;\n"
724 << " input [3:0] nibble;\n"
725 << " begin\n"
726 << " hex_char = (nibble < 4'd10) ? (8'd48 + nibble) : (8'd55 + nibble);\n"
727 << " end\n"
728 << " endfunction\n\n"
729 << " localparam TX_IDLE = 2'd0, TX_LOAD = 2'd1, TX_WAIT = 2'd2, TX_NEXT = 2'd3;\n"
730 << " reg [1:0] tx_state = TX_IDLE;\n\n"
731 << " always @(posedge clk) begin\n"
732 << " tx_start_r <= 1'b0;\n"
733 << " if (rst_btn) begin\n"
734 << " tx_state <= TX_IDLE;\n"
735 << " tx_ptr <= 7'd0;\n"
736 << " end else begin\n"
737 << " case (tx_state)\n"
738 << " TX_IDLE: if (tx_send) begin tx_ptr <= 7'd0; tx_state <= TX_LOAD; end\n"
739 << " TX_LOAD: if (!tx_busy) begin\n"
740 << " tx_data_r <= tx_buf[tx_ptr];\n"
741 << " tx_start_r <= 1'b1;\n"
742 << " tx_state <= TX_WAIT;\n"
743 << " end\n"
744 << " TX_WAIT: if (tx_busy) tx_state <= TX_NEXT;\n"
745 << " TX_NEXT: if (!tx_busy) begin\n"
746 << " if (tx_ptr + 1 < tx_len) begin\n"
747 << " tx_ptr <= tx_ptr + 1;\n"
748 << " tx_state <= TX_LOAD;\n"
749 << " end else\n"
750 << " tx_state <= TX_IDLE;\n"
751 << " end\n"
752 << " endcase\n"
753 << " end\n"
754 << " end\n\n";
755
756 // build_response Task
757 // The task is parameterised on the actual regex count at code-gen time
758 out << " // build_response Task\n"
759 << " // Fills tx_buf with the ASCII response packet in one clock cycle.\n"
760 << " // Format: \"MATCH=<bits> BYTES=<8hex> HITS=<4hex per regex,csv>\\r\\n\"\n"
761 << " reg [6:0] bp; // build pointer (local to task scope)\n"
762 << " reg [15:0] tmp16;\n"
763 << " integer ri;\n\n"
764 << " task build_response;\n"
765 << " input [" << busHigh << ":0] mbits;\n"
766 << " input [31:0] bcount;\n"
767 << " integer k;\n"
768 << " begin : build_task\n"
769 << " integer p;\n"
770 << " p = 0;\n"
771 << " // \"MATCH=\"\n"
772 << " tx_buf[p]=8'h4D; p=p+1; // M\n"
773 << " tx_buf[p]=8'h41; p=p+1; // A\n"
774 << " tx_buf[p]=8'h54; p=p+1; // T\n"
775 << " tx_buf[p]=8'h43; p=p+1; // C\n"
776 << " tx_buf[p]=8'h48; p=p+1; // H\n"
777 << " tx_buf[p]=8'h3D; p=p+1; // =\n"
778 << " // match bits, MSB first\n";
779
780 // Unroll the match-bit loop at C++ code-gen time for exact bit count
781 for (int b = static_cast<int>(numNFAs) - 1; b >= 0; --b)
782 {
783 out << " tx_buf[p] = (mbits[" << b << "]) ? 8'h31 : 8'h30; p=p+1;\n";
784 }
785
786 out << " // \" BYTES=\"\n"
787 << " tx_buf[p]=8'h20; p=p+1;\n"
788 << " tx_buf[p]=8'h42; p=p+1; // B\n"
789 << " tx_buf[p]=8'h59; p=p+1; // Y\n"
790 << " tx_buf[p]=8'h54; p=p+1; // T\n"
791 << " tx_buf[p]=8'h45; p=p+1; // E\n"
792 << " tx_buf[p]=8'h53; p=p+1; // S\n"
793 << " tx_buf[p]=8'h3D; p=p+1; // =\n"
794 << " tx_buf[p]=hex_char(bcount[31:28]); p=p+1;\n"
795 << " tx_buf[p]=hex_char(bcount[27:24]); p=p+1;\n"
796 << " tx_buf[p]=hex_char(bcount[23:20]); p=p+1;\n"
797 << " tx_buf[p]=hex_char(bcount[19:16]); p=p+1;\n"
798 << " tx_buf[p]=hex_char(bcount[15:12]); p=p+1;\n"
799 << " tx_buf[p]=hex_char(bcount[11: 8]); p=p+1;\n"
800 << " tx_buf[p]=hex_char(bcount[ 7: 4]); p=p+1;\n"
801 << " tx_buf[p]=hex_char(bcount[ 3: 0]); p=p+1;\n"
802 << " // \" HITS=\"\n"
803 << " tx_buf[p]=8'h20; p=p+1;\n"
804 << " tx_buf[p]=8'h48; p=p+1; // H\n"
805 << " tx_buf[p]=8'h49; p=p+1; // I\n"
806 << " tx_buf[p]=8'h54; p=p+1; // T\n"
807 << " tx_buf[p]=8'h53; p=p+1; // S\n"
808 << " tx_buf[p]=8'h3D; p=p+1; // =\n";
809
810 // Unroll the per-regex hits loop at C++ code-gen time
811 for (size_t k = 0; k < numNFAs; ++k)
812 {
813 out << " tmp16 = match_count[" << k << "];\n"
814 << " tx_buf[p]=hex_char(tmp16[15:12]); p=p+1;\n"
815 << " tx_buf[p]=hex_char(tmp16[11: 8]); p=p+1;\n"
816 << " tx_buf[p]=hex_char(tmp16[ 7: 4]); p=p+1;\n"
817 << " tx_buf[p]=hex_char(tmp16[ 3: 0]); p=p+1;\n";
818 if (k < numNFAs - 1)
819 out << " tx_buf[p]=8'h2C; p=p+1; // ','\n";
820 }
821
822 out << " tx_buf[p]=8'h0D; p=p+1; // CR\n"
823 << " tx_buf[p]=8'h0A; p=p+1; // LF\n"
824 << " tx_len = p[6:0];\n"
825 << " end\n"
826 << " endtask\n\n";
827
828 // Main Control FSM
829 out << " // Main Control FSM\n"
830 << " localparam S_IDLE = 4'd0;\n"
831 << " localparam S_FETCH = 4'd1;\n"
832 << " localparam S_DECODE = 4'd2;\n"
833 << " localparam S_CHAR_LOAD = 4'd3;\n"
834 << " localparam S_CHAR_STEP = 4'd4;\n"
835 << " localparam S_EOL_END = 4'd5;\n"
836 << " localparam S_EOL_MATCH = 4'd6;\n"
837 << " localparam S_EOL_LATCH = 4'd7;\n"
838 << " localparam S_TX_ARM = 4'd8;\n"
839 << " localparam S_TX_WAIT = 4'd9;\n"
840 << " localparam S_RESET_NFA = 4'd10;\n"
841 << " localparam S_QUERY_TX = 4'd11;\n\n"
842 << " reg [3:0] state = S_RESET_NFA;\n\n"
843 << " reg [" << busHigh << ":0] snap_match = " << numNFAs << "'b0;\n"
844 << " reg [31:0] snap_bytes = 32'd0;\n"
845 << " integer k;\n\n"
846 << " always @(posedge clk) begin\n"
847 << " tx_send <= 1'b0;\n"
848 << " fifo_rd_en <= 1'b0;\n"
849 << " nfa_en <= 1'b0;\n"
850 << " nfa_start <= 1'b0;\n\n"
851 << " if (rst_btn) begin\n"
852 << " state <= S_RESET_NFA;\n"
853 << " match_leds <= " << numNFAs << "'b0;\n"
854 << " byte_count <= 32'd0;\n"
855 << " for (k = 0; k < 16; k = k + 1)\n"
856 << " match_count[k] <= 16'd0;\n"
857 << " end else begin\n"
858 << " case (state)\n\n"
859 << " S_IDLE: begin\n"
860 << " nfa_end_of_str <= 1'b0;\n"
861 << " if (!fifo_empty) state <= S_FETCH;\n"
862 << " end\n\n"
863 << " S_FETCH: begin\n"
864 << " fifo_rd_en <= 1'b1;\n"
865 << " state <= S_DECODE;\n"
866 << " end\n\n"
867 << " S_DECODE: begin\n"
868 << " if (fifo_rd_data == 8'h0A || fifo_rd_data == 8'h0D) begin\n"
869 << " nfa_end_of_str <= 1'b1;\n"
870 << " state <= S_EOL_END;\n"
871 << " end else if (fifo_rd_data == 8'h3F) begin // '?'\n"
872 << " state <= S_QUERY_TX;\n"
873 << " end else begin\n"
874 << " nfa_char_in <= fifo_rd_data;\n"
875 << " state <= S_CHAR_LOAD;\n"
876 << " end\n"
877 << " end\n\n"
878 << " S_CHAR_LOAD: state <= S_CHAR_STEP;\n\n"
879 << " S_CHAR_STEP: begin\n"
880 << " nfa_en <= 1'b1;\n"
881 << " byte_count <= byte_count + 1;\n"
882 << " state <= S_IDLE;\n"
883 << " end\n\n"
884 << " S_EOL_END: begin\n"
885 << " nfa_en <= 1'b1;\n"
886 << " state <= S_EOL_MATCH;\n"
887 << " end\n\n"
888 << " S_EOL_MATCH: begin\n"
889 << " nfa_en <= 1'b1;\n"
890 << " nfa_end_of_str <= 1'b0;\n"
891 << " state <= S_EOL_LATCH;\n"
892 << " end\n\n"
893 << " S_EOL_LATCH: begin\n"
894 << " snap_match <= match_bus;\n"
895 << " snap_bytes <= byte_count;\n"
896 << " match_leds <= match_bus;\n"
897 << " for (k = 0; k < " << numNFAs << "; k = k + 1)\n"
898 << " if (match_bus[k]) match_count[k] <= match_count[k] + 16'd1;\n"
899 << " state <= S_TX_ARM;\n"
900 << " end\n\n"
901 << " S_TX_ARM: begin\n"
902 << " build_response(snap_match, snap_bytes);\n"
903 << " tx_send <= 1'b1;\n"
904 << " state <= S_TX_WAIT;\n"
905 << " end\n\n"
906 << " S_TX_WAIT:\n"
907 << " if (tx_state == TX_IDLE && !tx_send) state <= S_RESET_NFA;\n\n"
908 << " S_RESET_NFA: begin\n"
909 << " nfa_start <= 1'b1;\n"
910 << " nfa_en <= 1'b1;\n"
911 << " state <= S_IDLE;\n"
912 << " end\n\n"
913 << " S_QUERY_TX: begin\n"
914 << " build_response(" << numNFAs << "'b0, byte_count);\n"
915 << " tx_send <= 1'b1;\n"
916 << " state <= S_TX_WAIT;\n"
917 << " end\n\n"
918 << " default: state <= S_IDLE;\n"
919 << " endcase\n"
920 << " end\n"
921 << " end\n\n"
922 << "endmodule\n";
923}
924
925// =============================================================================
926// emitConstraints - constraints.xdc
927// =============================================================================
928void Emitter::emitConstraints(const std::vector<std::unique_ptr<NFA>> &nfas, const std::filesystem::path &outputDir)
929{
930 auto filePath = outputDir / "constraints.xdc";
931 std::ofstream out(filePath);
932 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
933
934 out << "## Clock signal (Nexys A7 100MHz)\n"
935 << "set_property PACKAGE_PIN E3 [get_ports clk]\n"
936 << "set_property IOSTANDARD LVCMOS33 [get_ports clk]\n"
937 << "create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports clk]\n\n";
938
939 out << "## USB-RS232 Interface (Nexys A7)\n"
940 << "set_property PACKAGE_PIN C4 [get_ports uart_rx_pin]\n"
941 << "set_property IOSTANDARD LVCMOS33 [get_ports uart_rx_pin]\n\n"
942 << "set_property PACKAGE_PIN D4 [get_ports uart_tx_pin]\n"
943 << "set_property IOSTANDARD LVCMOS33 [get_ports uart_tx_pin]\n\n";
944
945 out << "## Buttons (Nexys A7 Center Button - BTNC)\n"
946 << "set_property PACKAGE_PIN N17 [get_ports rst_btn]\n"
947 << "set_property IOSTANDARD LVCMOS33 [get_ports rst_btn]\n\n";
948
949 out << "## LEDs (Nexys A7 LED0 to LED15)\n";
950 std::vector<std::string> led_pins = {
951 "H17", "K15", "J13", "N14", "R18", "V17", "U17", "U16",
952 "V16", "T15", "U14", "T16", "V15", "V14", "V12", "V11"};
953 for (size_t i = 0; i < nfas.size() && i < led_pins.size(); ++i)
954 {
955 out << "set_property PACKAGE_PIN " << led_pins[i] << " [get_ports {match_leds[" << i << "]}]\n"
956 << "set_property IOSTANDARD LVCMOS33 [get_ports {match_leds[" << i << "]}]\n";
957 }
958}
959
960void Emitter::emitPIIUART(const std::filesystem::path &outputDir)
961{
962 auto filePath = outputDir / "uart_rx.v";
963 std::ofstream out(filePath);
964 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
965
966 out << "`timescale 1ns / 1ps\n\n";
967 out << R"(
968module uart_rx #(
969 parameter CLKS_PER_BIT = 868 // 100 MHz / 115200 Baud
970)(
971 input wire clk,
972 input wire rx,
973 output reg [7:0] rx_data,
974 output reg rx_ready
975);
976 localparam IDLE = 2'b00, START_BIT = 2'b01, DATA_BITS = 2'b10, STOP_BIT = 2'b11;
977 reg [1:0] state = IDLE;
978 reg [9:0] clk_count = 0;
979 reg [2:0] bit_idx = 0;
980
981 always @(posedge clk) begin
982 case (state)
983 IDLE: begin
984 rx_ready <= 1'b0;
985 clk_count <= 0;
986 bit_idx <= 0;
987 if (rx == 1'b0) state <= START_BIT;
988 end
989 START_BIT: begin
990 if (clk_count == (CLKS_PER_BIT-1)/2) begin
991 if (rx == 1'b0) begin
992 clk_count <= 0;
993 state <= DATA_BITS;
994 end else state <= IDLE;
995 end else clk_count <= clk_count + 1;
996 end
997 DATA_BITS: begin
998 if (clk_count < CLKS_PER_BIT-1) begin
999 clk_count <= clk_count + 1;
1000 end else begin
1001 clk_count <= 0;
1002 rx_data[bit_idx] <= rx;
1003 if (bit_idx < 7) bit_idx <= bit_idx + 1;
1004 else state <= STOP_BIT;
1005 end
1006 end
1007 STOP_BIT: begin
1008 if (clk_count < CLKS_PER_BIT-1) begin
1009 clk_count <= clk_count + 1;
1010 end else begin
1011 // FIX: Check for framing error
1012 if (rx == 1'b1) rx_ready <= 1'b1;
1013 state <= IDLE;
1014 end
1015 end
1016 endcase
1017 end
1018endmodule
1019)";
1020}
1021
1022void Emitter::emitPIIUARTTx(const std::filesystem::path &outputDir)
1023{
1024 auto filePath = outputDir / "uart_tx.v";
1025 std::ofstream out(filePath);
1026 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
1027
1028 out << "`timescale 1ns / 1ps\n\n";
1029 out << R"(
1030module uart_tx #(
1031 parameter CLKS_PER_BIT = 868 // 100 MHz / 115200 Baud
1032)(
1033 input wire clk,
1034 input wire tx_start,
1035 input wire [7:0] tx_data,
1036 output reg tx,
1037 output reg tx_busy
1038);
1039 localparam IDLE = 2'b00, START_BIT = 2'b01, DATA_BITS = 2'b10, STOP_BIT = 2'b11;
1040 reg [1:0] state = IDLE;
1041 reg [9:0] clk_count = 0;
1042 reg [2:0] bit_idx = 0;
1043 reg [7:0] tx_data_latch = 0;
1044
1045 initial begin
1046 tx = 1'b1;
1047 tx_busy = 1'b0;
1048 end
1049
1050 always @(posedge clk) begin
1051 case (state)
1052 IDLE: begin
1053 tx <= 1'b1;
1054 tx_busy <= 1'b0;
1055 if (tx_start) begin
1056 tx_data_latch <= tx_data;
1057 tx_busy <= 1'b1;
1058 state <= START_BIT;
1059 clk_count <= 0;
1060 end
1061 end
1062 START_BIT: begin
1063 tx <= 1'b0;
1064 if (clk_count < CLKS_PER_BIT-1) begin
1065 clk_count <= clk_count + 1;
1066 end else begin
1067 clk_count <= 0;
1068 bit_idx <= 0;
1069 state <= DATA_BITS;
1070 end
1071 end
1072 DATA_BITS: begin
1073 tx <= tx_data_latch[bit_idx];
1074 if (clk_count < CLKS_PER_BIT-1) begin
1075 clk_count <= clk_count + 1;
1076 end else begin
1077 clk_count <= 0;
1078 if (bit_idx < 7) bit_idx <= bit_idx + 1;
1079 else state <= STOP_BIT;
1080 end
1081 end
1082 STOP_BIT: begin
1083 tx <= 1'b1;
1084 if (clk_count < CLKS_PER_BIT-1) begin
1085 clk_count <= clk_count + 1;
1086 end else begin
1087 state <= IDLE;
1088 end
1089 end
1090 endcase
1091 end
1092endmodule
1093)";
1094}
1095
1096void Emitter::emitPIITopFPGA(const std::vector<std::unique_ptr<NFA>> &nfas, const std::filesystem::path &outputDir)
1097{
1098 auto filePath = outputDir / "top_fpga.v";
1099 std::ofstream out(filePath);
1100 out.exceptions(std::ofstream::failbit | std::ofstream::badbit);
1101
1102 size_t numNFAs = nfas.size();
1103
1104 out << "`timescale 1ns / 1ps\n\n";
1105 out << "module top_fpga (\n"
1106 << " input wire clk, // 100 MHz system clock\n"
1107 << " input wire rst_btn, // Physical reset button\n"
1108 << " input wire uart_rx_pin,// USB UART RX pin\n"
1109 << " output wire uart_tx_pin // USB UART TX pin\n"
1110 << ");\n\n";
1111
1112 out << R"(
1113 wire [7:0] rx_data;
1114 wire rx_ready;
1115
1116 uart_rx uart_inst (
1117 .clk(clk),
1118 .rx(uart_rx_pin),
1119 .rx_data(rx_data),
1120 .rx_ready(rx_ready)
1121 );
1122
1123 reg nfa_start = 1'b1;
1124 reg [7:0] nfa_char_in = 8'h00;
1125 reg nfa_en = 1'b0;
1126)";
1127
1128 out << " wire [" << (numNFAs > 0 ? numNFAs - 1 : 0) << ":0] match_bus;\n"
1129 << " wire [" << (numNFAs > 0 ? numNFAs - 1 : 0) << ":0] active_bus;\n\n";
1130
1131 out << " top regex_engine (\n"
1132 << " .clk(clk),\n"
1133 << " .en(nfa_en),\n"
1134 << " .rst(rst_btn),\n"
1135 << " .start(nfa_start),\n"
1136 << " .char_in(nfa_char_in),\n"
1137 << " .match_bus(match_bus),\n"
1138 << " .active_bus(active_bus)\n"
1139 << " );\n\n";
1140
1141 out << R"(
1142 // UART TX Signals
1143 reg tx_start = 1'b0;
1144 reg [7:0] tx_data = 8'h00;
1145 wire tx_busy;
1146
1147 uart_tx uart_tx_inst (
1148 .clk(clk),
1149 .tx_start(tx_start),
1150 .tx_data(tx_data),
1151 .tx(uart_tx_pin),
1152 .tx_busy(tx_busy)
1153 );
1154
1155 // 128-Byte Latency Buffering with Precise Alignment
1156 localparam DELAY_LEN = 128;
1157 (* ram_style = "distributed" *) reg [7:0] delay_bram [0:DELAY_LEN-1];
1158 reg [6:0] delay_ptr = 0;
1159 reg [DELAY_LEN-1:0] commit_history = 0;
1160
1161 // Per-NFA Active Histories to isolate redaction contexts
1162)";
1163 for (size_t i = 0; i < numNFAs; ++i)
1164 {
1165 out << " reg [DELAY_LEN-1:0] active_history_" << i << " = 0;\n";
1166 }
1167
1168 out << R"(
1169 // Power-On-Reset Initialization
1170 reg [7:0] por_count = 0;
1171 wire por_rst = (por_count < 8'hFF);
1172
1173 reg rx_ready_prev = 0;
1174 reg [1:0] step = 0;
1175 reg [7:0] rx_latch = 0;
1176
1177 always @(posedge clk) begin
1178 if (rst_btn || por_rst) begin
1179 if (por_count < 8'h80) begin
1180 delay_bram[por_count[6:0]] <= 8'h20; // Initialize BRAM sequentially
1181 end
1182 if (por_count < 8'hFF) por_count <= por_count + 1;
1183
1184 nfa_en <= 0;
1185 nfa_start <= 1;
1186 tx_start <= 0;
1187 rx_ready_prev <= 0;
1188 delay_ptr <= 0;
1189 commit_history <= 0;
1190 step <= 0;
1191)";
1192 for (size_t i = 0; i < numNFAs; ++i)
1193 {
1194 out << " active_history_" << i << " <= 0;\n";
1195 }
1196 out << R"(
1197 end else begin
1198 nfa_start <= 0;
1199 nfa_en <= 0;
1200 tx_start <= 0;
1201 rx_ready_prev <= rx_ready;
1202
1203 // Character Processing Sequence
1204 if (rx_ready && !rx_ready_prev) begin
1205 rx_latch <= rx_data;
1206 step <= 1;
1207 end
1208
1209 case (step)
1210 1: begin
1211 // Step 1: Feed NFA Engine
1212 nfa_char_in <= rx_latch;
1213 nfa_en <= 1;
1214 step <= 2;
1215 end
1216 2: begin
1217 // Step 2: NFA Results and Redaction Output
1218 // 1. Read delayed char and check redaction bit (Sampling BEFORE shift)
1219 tx_data <= (commit_history[DELAY_LEN-2]) ? 8'h58 : delay_bram[delay_ptr];
1220 tx_start <= 1;
1221
1222 // 2. Overwrite slot with new character
1223 delay_bram[delay_ptr] <= rx_latch;
1224 delay_ptr <= delay_ptr + 1;
1225
1226 // 3. Update Histories (Isolating contexts)
1227)";
1228 for (size_t i = 0; i < numNFAs; ++i)
1229 {
1230 out << " active_history_" << i << " <= active_bus[" << i << "] ? {active_history_" << i << "[DELAY_LEN-2:0], 1'b1} : 128'd0;\n";
1231 }
1232
1233 out << " commit_history <= {commit_history[DELAY_LEN-2:0], 1'b0}";
1234 for (size_t i = 0; i < numNFAs; ++i)
1235 {
1236 out << "\n | (match_bus[" << i << "] ? {active_history_" << i << "[DELAY_LEN-2:0], 1'b1} : 128'd0)";
1237 }
1238 out << ";\n";
1239
1240 out << R"(
1241 step <= 0;
1242 end
1243 endcase
1244 end
1245 end
1246endmodule
1247)";
1248}