//
// System Verilog Examples For Syntax Highlighting
// Taken from a hardware rasterizer project.
//
// Copyright (c) 2026 Samuel Fitzsimons (rainbain)
// SPDX-License-Identifier: MIT
//

`default_nettype none
`timescale 1ns/1ps

module raster #(
    parameter int ATTR_MAX_BITS = 32,

    // Example color generation
    // 1 sign bit, 8 color bits, 6 fractional bits
    parameter int NUM_ATTRS = 3,
    parameter int ATTR_BITS [NUM_ATTRS] = '{15, 15, 15},
    parameter int ATTR_FRAC [NUM_ATTRS] = '{6, 6, 6}
) (
    input logic clk,
    input logic rst_n,

    input  logic cmd_valid,
    output logic cmd_ready,

    input  logic       cmd_pipe_cmd,
    input  logic[7:0]  cmd_pipe,
    input  logic[7:0]  cmd_fmt,
    input  logic[4:0]  cmd_minx,
    input  logic[4:0]  cmd_miny,
    input  logic[4:0]  cmd_maxx,
    input  logic[4:0]  cmd_maxy,
    input  logic[17:0] cmd_w0_i,
    input  logic[17:0] cmd_w1_i,
    input  logic[17:0] cmd_w2_i,
    input  logic[9:0] cmd_dw0_dx,
    input  logic[9:0] cmd_dw1_dx,
    input  logic[9:0] cmd_dw2_dx,
    input  logic[9:0] cmd_dw0_dy,
    input  logic[9:0] cmd_dw1_dy,
    input  logic[9:0] cmd_dw2_dy,

    input logic [NUM_ATTRS-1:0][ATTR_MAX_BITS-1:0] cmd_attr_i,
    input logic [NUM_ATTRS-1:0][ATTR_MAX_BITS-1:0] cmd_attr_dx,
    input logic [NUM_ATTRS-1:0][ATTR_MAX_BITS-1:0] cmd_attr_dy,


    input  logic       frag_ready,
    output logic       frag_valid,

    output logic[4:0]  frag_x,
    output logic[4:0]  frag_y,
    output logic[7:0]  frag_fmt,

    output logic [NUM_ATTRS-1:0][ATTR_MAX_BITS-1:0] frag_attr,

    output logic       pipe_valid,
    input  logic       pipe_ready,
    output logic[7:0]  pipe
);
    logic[7:0]  fmt;

    // Tile and bounding box
    logic[4:0]  tile_x;
    logic[4:0]  tile_y;
    logic[4:0]  minx;
    logic[4:0]  maxx;
    logic[4:0]  maxy;

    // Edge Function Registers
    logic signed [17:0] w0_i;
    logic signed [17:0] w1_i;
    logic signed [17:0] w2_i;
    logic signed [17:0] w0;
    logic signed [17:0] w1;
    logic signed [17:0] w2;
    logic signed [9:0] dw0_dx;
    logic signed [9:0] dw1_dx;
    logic signed [9:0] dw2_dx;
    logic signed [9:0] dw0_dy;
    logic signed [9:0] dw1_dy;
    logic signed [9:0] dw2_dy;

    // Attribute Registers
    logic signed [NUM_ATTRS-1:0][ATTR_MAX_BITS-1:0] attr_i;
    logic signed [NUM_ATTRS-1:0][ATTR_MAX_BITS-1:0] attr;
    logic signed [NUM_ATTRS-1:0][ATTR_MAX_BITS-1:0] attr_dx;
    logic signed [NUM_ATTRS-1:0][ATTR_MAX_BITS-1:0] attr_dy;

    // Adder outputs
    logic signed [17:0] w0_next;
    logic signed [17:0] w1_next;
    logic signed [17:0] w2_next;
    logic signed [NUM_ATTRS-1:0][ATTR_MAX_BITS-1:0] attr_next;

    logic               raster_active;
    logic               vsync;
    logic               generate_output;

    always_ff @(posedge clk) begin
        if(cmd_valid && cmd_ready) begin
            raster_active <= !cmd_pipe_cmd;
            pipe_valid <= cmd_pipe_cmd;
            pipe <= cmd_pipe;
            cmd_ready <= 0;
            tile_x <= cmd_minx;
            tile_y <= cmd_miny;
            minx <= cmd_minx;
            maxx <= cmd_maxx;
            maxy <= cmd_maxy;
            w0_i <= cmd_w0_i;
            w1_i <= cmd_w1_i;
            w2_i <= cmd_w2_i;
            w0 <= cmd_w0_i;
            w1 <= cmd_w1_i;
            w2 <= cmd_w2_i;
            dw0_dx <= cmd_dw0_dx;
            dw1_dx <= cmd_dw1_dx;
            dw2_dx <= cmd_dw2_dx;
            dw0_dy <= cmd_dw0_dy;
            dw1_dy <= cmd_dw1_dy;
            dw2_dy <= cmd_dw2_dy;
            attr_i <= cmd_attr_i;
            attr <= cmd_attr_i;
            attr_dx <= cmd_attr_dx;
            attr_dy <= cmd_attr_dy;
            fmt <= cmd_fmt;
            vsync <= 0;
        end

        if(pipe_ready && pipe_valid) begin
            pipe_valid <= 0;
            cmd_ready <= 1;
        end

        // Clear valid? May be overridden
        if(frag_valid && frag_ready) begin
            frag_valid <= 0;
        end

        if(vsync) begin
            vsync <= 0;

            w0_i <= w0_next;
            w1_i <= w1_next;
            w2_i <= w2_next;
            w0 <= w0_next;
            w1 <= w1_next;
            w2 <= w2_next;

            attr <= attr_next;
            attr_i <= attr_next;
        end

        if(raster_active && (!frag_valid || frag_ready) && !vsync) begin
            // Step X Counters
            w0 <= w0_next;
            w1 <= w1_next;
            w2 <= w2_next;

            attr <= attr_next;

            tile_x <= tile_x + 1;
            if(tile_x == maxx) begin
                tile_x <= minx;
                tile_y <= tile_y + 1;
                if(tile_y == maxy) begin
                    // Last pixel
                    cmd_ready <= 1;
                    raster_active <= 0;
                end else begin
                    // Go down a row of pixels
                    vsync <= 1;
                end
            end
        end

        if(generate_output) begin
            frag_x <= tile_x;
            frag_y <= tile_y;
            frag_fmt <= fmt;
            frag_valid <= 1;
        end

        if(!rst_n) begin
            cmd_ready <= 1;
            frag_valid <= 0;
            raster_active <= 0;
            pipe_valid <= 0;
            vsync <= 0;
        end
    end

    always_comb begin
        generate_output = raster_active && (!frag_valid || frag_ready) && !vsync && w0 >= 0 && w1 >= 0 && w2 >= 0;

        w0_next = (vsync ? w0_i : w0) + (vsync ? 18'(dw0_dy) : 18'(dw0_dx));
        w1_next = (vsync ? w1_i : w1) + (vsync ? 18'(dw1_dy) : 18'(dw1_dx));
        w2_next = (vsync ? w2_i : w2) + (vsync ? 18'(dw2_dy) : 18'(dw2_dx));

        // Each attribute adder
        for (int i = 0; i < NUM_ATTRS; i++) begin
            attr_next[i] = (vsync ? attr_i[i] : attr[i]) + (vsync ? attr_dy[i] : attr_dx[i]);
        end
    end

    // Connect the attribute output
    // Only connecting the used bits
    // So that the unused adder bits get optimized out
    generate
        for (genvar i = 0; i < NUM_ATTRS; i++) begin : gen_attr
            always_comb begin
                frag_attr[i][ATTR_MAX_BITS-1:ATTR_BITS[i]] = '0;
            end

            always_ff @(posedge clk) begin
                if(generate_output) begin
                    frag_attr[i][ATTR_BITS[i]-1:0] <= attr[i][ATTR_BITS[i]-1:0];
                end
            end
        end
    endgenerate
endmodule

`define HALF_PERIOD 5

//
// Some components of the testbench for syntax highlighting of other system verilog features
module raster_tb;
    function automatic void show_cmd(input int t);
        if (cmd_log[t % MAX_CMD].pipe_cmd)
            $display("  command %0d: pipe token %h", t, cmd_log[t % MAX_CMD].pipe);
        else
            $display("  command %0d: triangle a=(%0d,%0d) b=(%0d,%0d) c=(%0d,%0d), tile at (%0d,%0d)", t,
                     cmd_vtx[t % MAX_CMD][0], cmd_vtx[t % MAX_CMD][1], cmd_vtx[t % MAX_CMD][2],
                     cmd_vtx[t % MAX_CMD][3], cmd_vtx[t % MAX_CMD][4], cmd_vtx[t % MAX_CMD][5],
                     cmd_tile[t % MAX_CMD][0], cmd_tile[t % MAX_CMD][1]);
    endfunction

    task automatic add_random_triangle();
        int ax, ay, bx, by, cx, cy;
        int pick;
        int skip;
        do begin
            ax = int'($urandom_range(SCREEN_W - 1));
            ay = int'($urandom_range(SCREEN_H - 1));
            pick = int'($urandom_range(99));
            if (pick < 25) begin
                // anywhere on screen
                bx = int'($urandom_range(SCREEN_W - 1));  by = int'($urandom_range(SCREEN_H - 1));
                cx = int'($urandom_range(SCREEN_W - 1));  cy = int'($urandom_range(SCREEN_H - 1));
            end else if (pick < 35) begin
                // huge, near three of the four screen corners, for the largest weights
                skip = int'($urandom_range(3));
                ax = near(CORNER_X[(skip + 1) % 4], 20, SCREEN_W - 1);
                ay = near(CORNER_Y[(skip + 1) % 4], 20, SCREEN_H - 1);
                bx = near(CORNER_X[(skip + 2) % 4], 20, SCREEN_W - 1);
                by = near(CORNER_Y[(skip + 2) % 4], 20, SCREEN_H - 1);
                cx = near(CORNER_X[(skip + 3) % 4], 20, SCREEN_W - 1);
                cy = near(CORNER_Y[(skip + 3) % 4], 20, SCREEN_H - 1);
            end else if (pick < 55) begin
                // small
                bx = near(ax, 8, SCREEN_W - 1);  by = near(ay, 8, SCREEN_H - 1);
                cx = near(ax, 8, SCREEN_W - 1);  cy = near(ay, 8, SCREEN_H - 1);
            end else if (pick < 70) begin
                // medium, about a tile
                bx = near(ax, 60, SCREEN_W - 1);  by = near(ay, 60, SCREEN_H - 1);
                cx = near(ax, 60, SCREEN_W - 1);  cy = near(ay, 60, SCREEN_H - 1);
            end else if (pick < 85) begin
                // sliver: two vertices next to each other
                bx = int'($urandom_range(SCREEN_W - 1));  by = int'($urandom_range(SCREEN_H - 1));
                cx = near(bx, 1, SCREEN_W - 1);           cy = near(by, 1, SCREEN_H - 1);
            end else begin
                // vertices on or next to tile boundaries
                ax = on_tile_edge(SCREEN_W - 1);  ay = on_tile_edge(SCREEN_H - 1);
                bx = on_tile_edge(SCREEN_W - 1);  by = on_tile_edge(SCREEN_H - 1);
                cx = on_tile_edge(SCREEN_W - 1);  cy = on_tile_edge(SCREEN_H - 1);
            end
        end while (tri_area(ax, ay, bx, by, cx, cy) == 0);
        add_tri_random_tile(ax, ay, bx, by, cx, cy, 24'($urandom), 24'($urandom), 24'($urandom));
    endtask


    // Sized literals in each base, a typedef and a case statement
    localparam logic [7:0] HEX = 8'hA5;
    localparam logic [3:0] BIN = 4'b10xz;
    localparam logic [5:0] OCT = 6'o17;
    localparam logic [7:0] DEC = 8'd200;

    typedef enum logic [1:0] {IDLE, RUN, DONE} state_t;
    state_t state;
    logic   clk = 0;
    logic   rst_n;

    /* Instance with named port connections */
    raster #(.NUM_ATTRS(3)) dut (
        .clk   (clk),
        .rst_n (rst_n)
    );

    initial forever #`HALF_PERIOD clk = ~clk;

    always_ff @(posedge clk) begin
        unique case (state)
            IDLE:    state <= RUN;
            RUN:     state <= DONE;
            default: state <= IDLE;
        endcase
    end
endmodule