// uuid256_tb.v — testbench for uuid256.v (Icarus Verilog).
//
//   iverilog -o /tmp/uuid256.vvp uuid256.v uuid256_tb.v
//   vvp /tmp/uuid256.vvp +selftest              # spec §11 vectors, §3.2/§6 parser rules, 3 live ids
//   vvp /tmp/uuid256.vvp +gen=100000            # print N ids (real entropy: /dev/urandom via $fread), one per line —
//                                               #   pipe into the Python strict parser to validate/dedup, as the asm targets do
//   vvp /tmp/uuid256.vvp +parse=<text> [+lenient]   # drive uuid256_parse: prints "ok <64 hex>" or "error <kind>"
`timescale 1ns/1ps

module uuid256_tb;
    reg  [255:0] raw;
    wire [255:0] id;
    wire         strict;
    wire [68*8-1:0] text;
    reg  [68*8-1:0] ptext;
    reg  [6:0]   plen;
    reg          pstrict;
    wire [255:0] pid;
    wire [2:0]   perr;

    uuid256_apply     u_apply  (.raw(raw), .id(id));
    uuid256_is_strict u_strict (.id(id), .strict(strict));
    uuid256_format    u_fmt    (.id(id), .text(text));
    uuid256_parse     u_parse  (.text(ptext), .len(plen), .strict(pstrict), .id(pid), .err(perr));

    integer fd, n, k, fails, r;
    reg [255:0] entropy;
    reg [128*8-1:0] argtxt;      // +parse= input (right-justified by $value$plusargs), up to 128 chars
    integer alen;

    // parse helper: sets inputs, waits for combinational settle, returns err
    task do_parse(input [68*8-1:0] t, input [6:0] l, input s); begin ptext = t; plen = l; pstrict = s; #1; end endtask

    task vector(input integer no, input [255:0] rawv, input [68*8-1:0] expected);
        reg ok;
        begin
            raw = rawv; #1;
            ok = (text == expected) && strict;
            do_parse(text, 68, 1);        ok = ok && perr == 0 && pid == id;                 // canonical strict round-trip
            do_parse(upcase(text), 68, 1); ok = ok && perr == 0 && pid == id;                 // uppercase accepted
            do_parse({hex64(rawv), 32'b0}, 64, 0); ok = ok && perr == 0 && pid == rawv;      // raw compact lenient ok
            do_parse({hex64(rawv), 32'b0}, 64, 1); ok = ok && perr == 4;                     // raw compact strict → version
            $display("  spec §11 vector %0d: %s  %s", no, ok ? "PASS" : "FAIL", text);
            if (!ok) fails = fails + 1;
        end
    endtask

    function [68*8-1:0] upcase (input [68*8-1:0] t);
        integer i; reg [7:0] c;
        begin
            upcase = t;
            for (i = 0; i < 68; i = i + 1) begin
                c = t[543 - 8*i -: 8];
                if (c >= 8'h61 && c <= 8'h7a) upcase[543 - 8*i -: 8] = c - 8'h20;
            end
        end
    endfunction

    function [64*8-1:0] hex64 (input [255:0] v);   // compact lowercase hex of a raw 256-bit value
        integer i; reg [3:0] nb;
        begin
            for (i = 0; i < 64; i = i + 1) begin
                nb = v[255 - 4*i -: 4];
                hex64[511 - 8*i -: 8] = (nb < 10) ? (8'h30 + nb) : (8'h61 + nb - 10);
            end
        end
    endfunction

    task read_entropy;   // 32 bytes from the host OS CSPRNG (§5.2)
        begin
            r = $fread(entropy, fd);
            if (r != 32) begin $display("uuid256_tb: short read from /dev/urandom"); $finish(1); end
        end
    endtask

    initial begin
        fails = 0;
        fd = $fopen("/dev/urandom", "rb");
        if (fd == 0) begin $display("uuid256_tb: cannot open /dev/urandom"); $finish(1); end

        if ($test$plusargs("selftest")) begin
            $display("UUID256 reference implementation (Verilog, Icarus simulation) — README.md (256-bit random, 16-8-8-8-24 text)\n");
            $display("Self-tests:");
            vector(1, 256'h000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f, "0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f");
            vector(2, 256'hfffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0, "fffefdfcfbfaf9f8-f7f6f5f4-43f2f1f0-afeeedec-ebeae9e8e7e6e5e4e3e2e1e0");
            begin : rules
                reg ok; reg [68*8-1:0] niltxt;
                ok = 1;
                do_parse("0001020304050607_08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f", 68, 1); ok = ok && perr == 2;
                do_parse({"0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1", 8'b0}, 67, 1); ok = ok && perr == 1;
                do_parse("0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1g", 68, 1); ok = ok && perr == 3;
                niltxt = "0000000000000000-00000000-00000000-00000000-000000000000000000000000";
                do_parse(niltxt, 68, 1); ok = ok && perr == 4;
                do_parse(niltxt, 68, 0); ok = ok && perr == 0 && pid == 256'd0;
                raw = {256{1'b1}}; #1;                                     // max: formatter output; apply() changes 6 bits so
                ok = ok && (u_fmt.text[543:512] == "ffff");                 //   just check the untouched leading group here
                $display("  parser rules (§3.2/§6):  %s", ok ? "PASS" : "FAIL");
                if (!ok) fails = fails + 1;
            end
            for (k = 0; k < 3; k = k + 1) begin : live
                reg ok;
                read_entropy; raw = entropy; #1;
                do_parse(text, 68, 1);
                // string offsets: hex digit 24 → char 26 (after 2 hyphens), hex digit 32 → char 35 (after 3 hyphens)
                ok = strict && text[543-8*26 -: 8] == "4" && (text[543-8*35 -: 8] == "8" || text[543-8*35 -: 8] == "9" ||
                     text[543-8*35 -: 8] == "a" || text[543-8*35 -: 8] == "b") && perr == 0 && pid == id;
                $display("  generate: %s  %s", text, ok ? "ok" : "BAD");
                if (!ok) fails = fails + 1;
            end
            if (fails > 0) begin $display("  self-test FAILED"); $finish(1); end
            $display("\nall Verilog self-tests passed");
        end

        if ($value$plusargs("parse=%s", argtxt) || $test$plusargs("parse=")) begin
            // length = number of non-zero bytes (argv cannot contain NUL); then left-justify into the 68-char text port
            alen = 0;
            for (k = 0; k < 128; k = k + 1) if (argtxt[8*k +: 8] != 8'h00) alen = k + 1;
            if (alen > 127) alen = 127;
            ptext = {544{1'b0}};
            for (k = 0; k < 68; k = k + 1)
                if (k < alen) ptext[543 - 8*k -: 8] = argtxt[8*(alen - 1 - k) +: 8];
            plen = alen; pstrict = !$test$plusargs("lenient"); #1;
            case (perr)                              // (separate literals: %s of a ?: over unequal-length strings pads with spaces)
                0: $display("ok %h", pid);
                1: $display("error length");
                2: $display("error hyphen");
                3: $display("error char");
                default: $display("error version");
            endcase
            $fclose(fd);
            $finish(perr == 0 ? 0 : 1);
        end
        if ($value$plusargs("gen=%d", n)) begin
            for (k = 0; k < n; k = k + 1) begin
                read_entropy; raw = entropy; #1;
                $display("%s", text);
            end
        end
        $fclose(fd);
        $finish(0);
    end
endmodule
