module fractional_n_divider
    # (
        parameter WIDTH_INTEGER         = 10,
        parameter WIDTH_MODULUS         = 16,
        parameter ORDER                 = 3,
        parameter DATA_WIDTH            = WIDTH_INTEGER+WIDTH_MODULUS
    ) (
        input                       rst,

        // input and output frequencies
        input                       input_frequency,
        output                      output_frequency,

        // a manual interface for data
        input [DATA_WIDTH-1:0]      divide_value,
        input [1:0]                 dither_select,

        // test outputs
        output                      dither_output
    );

    // internal signals
    wire [WIDTH_INTEGER-1:0]        integer_value;
    wire [WIDTH_MODULUS-1:0]        fractional_value;
    wire signed [ORDER-1:0]         mash_output;
    wire [WIDTH_INTEGER-1:0]        count_target;
    reg [WIDTH_INTEGER-1:0]         count;
    reg                             output_state;
    reg                             dither;
    reg                             dither_en_lfsr;
    reg                             dither_in_lfsr;
    reg                             dither_en_trng;
    wire                            dither_out_lfsr;
    wire                            dither_out_trng;


    // split the divider values
    assign integer_value = divide_value[DATA_WIDTH-1:WIDTH_MODULUS];
    assign fractional_value = divide_value[WIDTH_MODULUS-1:0] + dither;

    // instantiate the MASH modulator
    mash_mod # (
        .WIDTH_MODULUS(WIDTH_MODULUS),
        .ORDER(ORDER)
    ) mash_mod_inst (
        .clk(output_frequency),
        .rst(rst),
        .data_in(fractional_value),
        .data_out(mash_output)
    );

    // combine the integer and fractional components
    assign count_target = $unsigned($signed(integer_value) + {{(WIDTH_INTEGER-ORDER){mash_output[ORDER-1]}}, mash_output});

    // count until the target is met and then output a pulse
    always @(posedge rst, posedge input_frequency) begin
        if (rst) begin
            count = 0;
            output_state = 0;
        end
        else begin
            if (count == count_target) begin
                count = 0;
                output_state = 1;
            end
            else begin
                count = count + 1;
                output_state = 0;
            end
        end
    end
    assign output_frequency = output_state;

    // LFSR based dither
    lfsr_fib lfsr_fib_inst (
        .i_clk(output_frequency),
        .i_reset(rst),
        .i_ce(dither_en_lfsr),
        .i_in(dither_in_lfsr),
        .o_bit(dither_out_lfsr)
    );

    // TRNG based dither
    trng trng_inst (
        .clk(output_frequency), 
        .rst(rst), 
        .stop(!dither_en_trng),
        .random(dither_out_trng)
    );

    // select the dither source
    always @(posedge output_frequency) begin
        case(dither_select)
            2'b00  :begin
                dither <= 0;
                dither_en_lfsr <= 0;
                dither_in_lfsr <= 0;
                dither_en_trng <= 0;
            end

            2'b01  : begin
                dither <= dither_out_trng;
                dither_en_lfsr <= 0;
                dither_in_lfsr <= 0;
                dither_en_trng <= 1;
            end

            2'b10  : begin
                dither <= dither_out_lfsr;
                dither_en_lfsr <= 1;
                dither_in_lfsr <= 0;
                dither_en_trng <= 0;
            end

            2'b11  : begin
                dither <= dither_out_lfsr;
                dither_en_lfsr <= 1;
                dither_in_lfsr <= 1;
                dither_en_trng <= 0;
            end

            default : begin
                dither <= 0;
                dither_en_lfsr <= 0;
                dither_in_lfsr <= 0;
                dither_en_trng <= 0;
            end
        endcase
    end

    assign dither_output = dither;

endmodule