/*
 *
 * Author:  Mark A. Indovina
 *          Rochester, NY, USA
 *
 */

module results_conv (
           clk,
           reset,
           rcc_clk,
           address,
           din,
           digit_clk,
           dout,
           dout_flag,
           test_mode,
           scan_in0,
           scan_in1,
           scan_en,
           scan_out0,
           scan_out1
       ) ;

/*
 *
 * Results Character Conversion (RCC)
 *  processes a computed frequency spectrum to
 *  determine if a valid DTMF digit can be found
 *
 */

input
    clk,                    // system clock
    reset,                  // system reset
    rcc_clk ;             // data input write strobe

input [3:0]
      address ;               // holding register address bus

input [15:0]
      din ;                   // data input bus

output
    digit_clk ;            // data output write strobe

output [7:0]
       dout ;                  // data output bus

output
    dout_flag ;             // data output change flag

input
    test_mode,		    // test mode control
    scan_in0,           // test scan mode data input, chain 1
    scan_in1,           // test scan mode data input, chain 2
    scan_en;            // test scan mode enable

output
    scan_out0,          // test scan mode data output, chain 1
    scan_out1;          // test scan mode data output, chain 2

reg
    digit_clk,
    dout_flag,
    go,
    gt,
    ok,
    clear_flag,
    seen_quiet,
    start_gt,
    gt_done,
    clear_gt,
    start_ct,
    ct_done,
    clear_ct;

reg [2:0]
    low,
    high ;

reg [4:0]
    state,
    save_state;

reg [2:0]
    gt_state,
    ct_state;

reg [7:0]
    dout,
    out_p1,    // two stage pipeline for digit/ quiet framing; I should
    out_p2;    //  have used dout as part of the pipeline to save area...

reg [15:0]
    r697,
    r770,
    r852,
    r941,
    r1209,
    r1336,
    r1477,
    r1633,
    low_mag,
    high_mag,
    opa,
    opb,
    opc,
    opd ;

`include "include/results_conv.h"

wire flag_reset = (reset | clear_flag) & !test_mode ;

always @(negedge rcc_clk or posedge flag_reset)
    if (flag_reset)
        go <= 0 ;
    else
        go <= address[3] ;

always @(negedge rcc_clk)
case (address[3:0])
    `R_697  : r697  <= din ;
    `R_770  : r770  <= din ;
    `R_852  : r852  <= din ;
    `R_941  : r941  <= din ;
    `R_1209 : r1209 <= din ;
    `R_1336 : r1336 <= din ;
    `R_1477 : r1477 <= din ;
    `R_1633 : r1633 <= din ;
endcase

always @(posedge reset or posedge clk)
begin : rcc_machine
    if (reset)
    begin
        digit_clk 		<= 0 ;
        dout_flag 		<= 1 ;
        clear_flag 	<= 0 ;
        seen_quiet 	<= 1 ;
        out_p1 		<= 0 ;
        out_p2 		<= 8'hff ;
        low			<= 0 ;
        high 			<= 0 ;
        low_mag		<= 0 ;
        high_mag		<= 0 ;
        opa			<= 0 ;
        opb			<= 0 ;
        opc			<= 0 ;
        opd			<= 0 ;
        start_gt		<= 0 ;
        clear_gt		<= 0 ;
        start_ct		<= 0 ;
        clear_ct		<= 0 ;
        dout 			<= 8'hff ;
        state			<= `IDLE ;
        save_state		<= `IDLE ;
    end
    else
    begin
        case (state)
            `IDLE : begin
                if (go)
                begin
                    low 		<= 3'b100 ;
                    high 		<= 3'b100 ;
                    clear_flag 	<= 1 ;
                    out_p2 		<= out_p1 ;  // digit pipeline
                    //gt_comp( r697, r770, r852, r941 ) ;
                    opa <= r697 ;
                    opb <= r770 ;
                    opc <= r852 ;
                    opd <= r941 ;
                    start_gt <= 1 ;
                    save_state 	<= `F1 ;
                    state 	<= `GT_WAIT ;
                end
                else
                begin
                    state	<= `IDLE ;
                end
            end
            `F1 : begin
                clear_flag 	<= 0 ;
                if (gt)
                begin
                    low 	<= {1'b0, `V_697} ;
                    low_mag <= r697 ;
                end
                //gt_comp( r770, r697, r852, r941 ) ;
                opa <= r770 ;
                opb <= r697 ;
                opc <= r852 ;
                opd <= r941 ;
                start_gt <= 1 ;
                save_state 	<= `F2 ;
                state 	<= `GT_WAIT ;
            end
            `F2 : begin
                if (gt)
                begin
                    low 	<= {1'b0, `V_770} ;
                    low_mag <= r770 ;
                end
                //gt_comp( r852, r697, r770, r941 ) ;
                opa <= r852 ;
                opb <= r697 ;
                opc <= r770 ;
                opd <= r941 ;
                start_gt <= 1 ;
                save_state 	<= `F3 ;
                state 	<= `GT_WAIT ;
            end
            `F3 : begin
                if (gt)
                begin
                    low 	<= {1'b0, `V_852} ;
                    low_mag <= r852 ;
                end
                //gt_comp( r941, r697, r770, r852 ) ;
                opa <= r941 ;
                opb <= r697 ;
                opc <= r770 ;
                opd <= r852 ;
                start_gt <= 1 ;
                save_state 	<= `F4 ;
                state 	<= `GT_WAIT ;
            end
            `F4 : begin
                if (gt)
                begin
                    low 	<= {1'b0, `V_941} ;
                    low_mag <= r941 ;
                end
                //gt_comp( r1209, r1336, r1477, r1633 ) ;
                opa <= r1209 ;
                opb <= r1336 ;
                opc <= r1477 ;
                opd <= r1633 ;
                start_gt <= 1 ;
                save_state 	<= `F5 ;
                state 	<= `GT_WAIT ;
            end
            `F5 : begin
                if (gt)
                begin
                    high 	 <= {1'b0, `V_1209} ;
                    high_mag <= r1209 ;
                end
                //gt_comp( r1336, r1209, r1477, r1633 ) ;
                opa <= r1336 ;
                opb <= r1209 ;
                opc <= r1477 ;
                opd <= r1633 ;
                start_gt <= 1 ;
                save_state 	<= `F6 ;
                state 	<= `GT_WAIT ;
            end
            `F6 : begin
                if (gt)
                begin
                    high 	 <= {1'b0, `V_1336} ;
                    high_mag <= r1336 ;
                end
                //gt_comp( r1477, r1209, r1336, r1633 ) ;
                opa <= r1477 ;
                opb <= r1209 ;
                opc <= r1336 ;
                opd <= r1633 ;
                start_gt <= 1 ;
                save_state 	<= `F7 ;
                state 	<= `GT_WAIT ;
            end
            `F7 : begin
                if (gt)
                begin
                    high 	<= {1'b0, `V_1477} ;
                    high_mag<= r1477 ;
                end
                //gt_comp( r1633, r1209, r1336, r1477 ) ;
                opa <= r1633 ;
                opb <= r1209 ;
                opc <= r1336 ;
                opd <= r1477 ;
                start_gt <= 1 ;
                save_state 	<= `F8 ;
                state 	<= `GT_WAIT ;
            end
            `F8 : begin
                if (gt)
                begin
                    high 	 <= {1'b0, `V_1633} ;
                    high_mag <= r1633 ;
                end
                state 	 <= `CHECK ;
            end
            // did we find both frequencies?
            `CHECK : begin
                if (!low[2] && !high[2])
                begin
                    //check_twist( low_mag, high_mag ) ;
                    opa <= low_mag ;
                    opb <= high_mag ;
                    start_ct <= 1 ;
                    save_state 	<= `OK ;
                    state 	<= `CT_WAIT ;
                end
                else
                begin
                    out_p1 <= `NO_DIGIT ;
                    state <= `CHARACTER ;
                end
            end

            `OK : begin
                if (ok)
                begin
                    case ({low[1:0], high[1:0]})
                        key_1[3:0]     : out_p1 <= val_key_1 ;
                        key_2[3:0]     : out_p1 <= val_key_2 ;
                        key_3[3:0]     : out_p1 <= val_key_3 ;
                        key_a[3:0]     : out_p1 <= val_key_a ;
                        key_4[3:0]     : out_p1 <= val_key_4 ;
                        key_5[3:0]     : out_p1 <= val_key_5 ;
                        key_6[3:0]     : out_p1 <= val_key_6 ;
                        key_b[3:0]     : out_p1 <= val_key_b ;
                        key_7[3:0]     : out_p1 <= val_key_7 ;
                        key_8[3:0]     : out_p1 <= val_key_8 ;
                        key_9[3:0]     : out_p1 <= val_key_9 ;
                        key_c[3:0]     : out_p1 <= val_key_c ;
                        key_star[3:0]  : out_p1 <= val_key_star ;
                        key_0[3:0]     : out_p1 <= val_key_0 ;
                        key_pound[3:0] : out_p1 <= val_key_pound ;
                        key_d[3:0]     : out_p1 <= val_key_d ;
                        default        : out_p1 <= `NO_DIGIT ;
                    endcase
                    state 	<= `CHARACTER ;
                end
                else
                begin
                    out_p1 <= `NO_DIGIT ;
                    state <= `CHARACTER ;
                end
            end
            // should we output a new digit?
            //  need to see two frames worth for timing...
            `CHARACTER : begin
                if (out_p1 == out_p2)
                begin
                    // quiet tone?
                    if (out_p2 == `NO_DIGIT)
                    begin
                        seen_quiet 	<= 1 ;
                        state <= `IDLE ;
                    end
                    else
                    begin
                        if (seen_quiet)
                        begin
                            seen_quiet 	<= 0 ;
                            state	<= `P1 ;

            end
            // toggle msb for each new char...
            `P1 : begin
                dout 		<= { 1'b0, out_p2[6:0] } ;
                dout_flag 	<= ~dout_flag ;
                state	<= `P2 ;
            end
            `P2 : begin
                digit_clk <= 1 ;
                state	<= `P3 ;
            end
            `P3 : begin
                digit_clk <= 0 ;
                state	<= `IDLE ;
            end
            // wait for greater_than to finish
            `GT_WAIT : begin
                if (gt_done)
                begin
                    start_gt <= 0 ;
                    clear_gt <= 1 ;
                    state <= `GT_FINISH ;
                end
                else
                    state <= `GT_WAIT ;
            end
            `GT_FINISH : begin
                clear_gt <= 0 ;
                state <= save_state ;
            end
            // wait for check_twist to finish
            `CT_WAIT : begin
                if (ct_done)
                begin
                    start_ct <= 0 ;
                    clear_ct <= 1 ;
                    state <= `CT_FINISH ;
                end
                else
                    state <= `CT_WAIT ;
            end
            `CT_FINISH : begin
                clear_ct <= 0 ;
                state <= save_state ;
            end
            default : 	state 	<= `IDLE ;
        endcase
    end
end // rcc_machine

//
// 16 bit "greater-than" comparision
// we'll build our own pipelined comparitor here
// (we want to force resource sharring...)
//

reg [16:0]
    cmpb,
    cmpc,
    cmpd ;

always @(posedge reset or posedge clk)
begin : gt_comp
    if (reset)
    begin
        cmpb 	<= 0 ;
        cmpc 	<= 0 ;
        cmpd 	<= 0 ;
        gt 		<= 0 ;
        gt_done	<= 0 ;
        gt_state  <= `GT_IDLE ;
    end
    else
    begin
        case (gt_state)
            `GT_IDLE : begin
                if (start_gt)
                    gt_state <= `GEN_B ;
                else
                    gt_state <= `GT_IDLE ;
            end
            `GEN_B : begin
                cmpb <= opb - opa ;
                gt_state <= `GEN_C ;
            end
            `GEN_C : begin
                cmpc <= opc - opa ;
                gt_state <= `GEN_D ;
            end
            `GEN_D : begin
                cmpd <= opd - opa ;
                gt_state <= `GT ;
            end
            `GT : begin
                gt <= cmpb[16] & cmpc[16] & cmpd[16] ;
                gt_state <= `GT_DONE ;
            end
            `GT_DONE : begin
                gt_done <= 1 ;
                gt_state <= `GT_CLEAR ;
            end
            `GT_CLEAR : begin
                if (clear_gt)
                begin
                    gt_done <= 0 ;
                    gt_state <= `GT_IDLE ;
                end
                else
                    gt_state <= `GT_CLEAR ;
            end
            default : 	gt_state <= `GT_IDLE ;
        endcase
    end
end

//
// check the twist between the frequencies,
// constrain to +/- 12dB for the now...
//

reg [16:0]
    cmpf,
    cmpr ;

always @(posedge reset or posedge clk)
begin : check_twist
    if (reset)
    begin
        cmpf 	<= 0 ;
        cmpr 	<= 0 ;
        ok 		<= 0 ;
        ct_done	<= 0 ;
        ct_state  <= `CT_IDLE ;
    end
    else
    begin
        case (ct_state)
            `CT_IDLE : begin
                if (start_ct)
                    ct_state <= `GEN_F ;
                else
                    ct_state <= `GT_IDLE ;
            end
            `GEN_F : begin
                cmpf <= opa - opb ;
                ct_state <= `GEN_R ;
            end
            `GEN_R : begin
                if (cmpf[16])   // high freq is larger
                begin
                    cmpf <= opa - {2'b0, opb[15:2]} ;
                    cmpr <= opb - opa ;
                    ct_state <= `CT ;
                end
                else            // low freq is larger
                begin
                    cmpf <= opb - {2'b0, opa[15:2]} ;
                    cmpr <= opa - opb ;
                    ct_state <= `CT ;
                end
            end
            `CT : begin
                ok <= (~cmpf[16]) && (~cmpr[16]) ;
                ct_state <= `CT_DONE ;
            end
            `CT_DONE : begin
                ct_done <= 1 ;
                ct_state <= `CT_CLEAR ;
            end
            `CT_CLEAR : begin
                if (clear_ct)
                begin
                    ct_done <= 0 ;
                    ct_state <= `CT_IDLE ;
                end
                else
                    ct_state <= `CT_CLEAR ;
            end
            default :	ct_state <= `CT_IDLE ;
        endcase
    end
end

endmodule // results_conv

                        end
                        else
                            state <= `IDLE ;
                    end


/*
 *
 * Author:  Mark A. Indovina
 *          Rochester, NY, USA
 *
 */

`timescale 1ns / 1ns

module test;

wire  digit_clk;

reg  clk, rcc_clk, reset, test_mode;

wire dout_flag;
wire [7:0]  dout;
wire [7:0]  dout_char = dout & 8'h7f ;

reg [3:0]  g_address, address, digcnt, qcnt;
reg [15:0]  g_din, din;
reg scan_in0, scan_in1, scan_en;

results_conv top (
                 .clk(clk),
                 .reset(reset),
                 .test_mode(test_mode),
                 .rcc_clk(rcc_clk),
                 .address(address),
                 .din(din),
                 .digit_clk(digit_clk),
                 .dout(dout),
`ifdef NETLIST
                 .dout_flag(dout_flag),
                 .scan_in0(scan_in0),
                 .scan_in1(scan_in1),
                 .scan_en(scan_en)
`else
                 .dout_flag(dout_flag)
`endif
             ) ;

initial
begin
    $timeformat(-9,2,"ns", 16);
`ifdef SDFSCAN
    $sdf_annotate("sdf/results_conv_tsmc18_scan.sdf", test.top);
`endif
    test_mode = 0;
    scan_in0 = 0;
    scan_in1 = 0;
    scan_en = 0;

    address[3:0] = 4'b0000;
    g_address[3:0] = 4'b0000;

    clk = 1'b0;
    din[15:0] = 16'b0000000000000000;

    reset = 1'b1;
    @(negedge clk) ;
    @(negedge clk) ;
    reset = 1'b0;

    repeat (1024)
    begin
        digcnt = $urandom ;
        repeat (digcnt)
        begin
            repeat (4)
                @(posedge clk);
            g_address = 0 ;
            g_din = $urandom ;
            repeat (8)
            begin
                write_it ;
                g_address = g_address + 1 ;
                g_din = $urandom ;
            end
            write_it ;
            g_address = 0 ;
            repeat (160)
                @(posedge clk) ;
        end
        qcnt = $urandom ;
        repeat (qcnt)
        begin
            repeat (4)
                @(posedge clk);
            g_address = 0 ;
            g_din = 0 ;
            repeat (8)
            begin
                write_it ;
                g_address = g_address + 1 ;
            end
            write_it ;
            g_address = 0 ;
            repeat (160)
                @(posedge clk) ;
        end
    end
    $stop ;
end

always #25 clk = ~clk ;

always @(posedge digit_clk)
begin
    #0 ;
    $display( "Time: %t", $time, ", Digit Clock: %b", digit_clk, ", Digit: \"%c\"", dout, ", Dout Flag: %b", dout_flag );
end

task write_it ;
    begin
        @(posedge clk) ;
        address <= g_address ;
        din <= g_din ;
        @(posedge clk)
         rcc_clk <= 1 ;
        @(posedge clk)
         rcc_clk <= 0 ;
        @(posedge clk) ;
        din <= 'bz ;
    end
endtask

endmodule


/*
 *
 * Author:  Mark A. Indovina
 *          Rochester, NY, USA
 *
 */

`timescale 1ns / 1ns

module test;

wire  digit_clk;

reg  clk, rcc_clk, reset, test_mode;

wire dout_flag;
wire [7:0]  dout;
wire [7:0]  dout_char = dout & 8'h7f ;

reg [3:0]  g_address, address, digcnt, qcnt;
reg [15:0]  g_din, din;
reg scan_in0, scan_in1, scan_en;

results_conv top (
                 .clk(clk),
                 .reset(reset),
                 .test_mode(test_mode),
                 .rcc_clk(rcc_clk),
                 .address(address),
                 .din(din),
                 .digit_clk(digit_clk),
                 .dout(dout),
`ifdef NETLIST
                 .dout_flag(dout_flag),
                 .scan_in0(scan_in0),
                 .scan_in1(scan_in1),
                 .scan_en(scan_en)
`else
                 .dout_flag(dout_flag)
`endif
             ) ;

initial
begin
    $timeformat(-9,2,"ns", 16);
`ifdef SDFSCAN
    $sdf_annotate("sdf/results_conv_tsmc18_scan.sdf", test.top);
`endif
    test_mode = 0;
    scan_in0 = 0;
    scan_in1 = 0;
    scan_en = 0;

    address[3:0] = 4'b0000;
    g_address[3:0] = 4'b0000;

    clk = 1'b0;
    din[15:0] = 16'b0000000000000000;
    g_din[15:0] = 16'b0000000000000000;

    rcc_clk = 1'b0;
    test_mode = 1'b0;
    reset = 1'b0;
    @(negedge clk) ;
    @(negedge clk) ;
    reset = 1'b1;
    @(negedge clk) ;
    @(negedge clk) ;
    reset = 1'b0;

    repeat (1024)
    begin
        digcnt = $random ;
        repeat (digcnt)
        begin
            repeat (4)
                @(posedge clk);
            g_address = 0 ;
            g_din = $random ;
            repeat (8)
            begin
                write_it ;
                g_address = g_address + 1 ;
                g_din = $random ;
            end
            write_it ;
            g_address = 0 ;
            repeat (160)
                @(posedge clk) ;
        end
        qcnt = $random ;
        repeat (qcnt)
        begin
            repeat (4)
                @(posedge clk);
            g_address = 0 ;
            g_din = 0 ;
            repeat (8)
            begin
                write_it ;
                g_address = g_address + 1 ;
            end
            write_it ;
            g_address = 0 ;
            repeat (160)
                @(posedge clk) ;
        end
    end
    $stop ;
end

always #25 clk = ~clk ;

    $display( "Time: %t", $time, ", Digit Clock: %b", digit_clk, ", Digit: \"%c\"", dout, ", Dout Flag: %b", dout_flag );
end

task write_it ;
    begin
        @(posedge clk) ;
        address <= g_address ;
        din <= g_din ;
        @(posedge clk)
         rcc_clk <= 1 ;
        @(posedge clk)
         rcc_clk <= 0 ;
        @(posedge clk) ;
        din <= 'bz ;
    end
endtask

endmodule


module adder(input_if.port inter, output_if.port out_inter, output state);
    enum logic [1:0] {INITIAL,WAIT,SEND} state;

    always_ff @(posedge inter.clk)
        if(inter.rst) begin
            inter.ready <= 0;
            out_inter.data <= 'x;
            out_inter.valid <= 0;
            state <= INITIAL;
        end
        else case(state)

                WAIT: begin
                    if(inter.valid) begin
                        inter.ready <= 0;
                        out_inter.data <= inter.A + inter.B;
                        out_inter.valid <= 1;
                        state <= SEND;
                    end
                end

                SEND: begin
                    if(out_inter.ready) begin
                        out_inter.valid <= 0;
                        inter.ready <= 1;
                        state <= WAIT;
                    end
                end
        endcase
endmodule: adder

                INITIAL: begin
                    inter.ready <= 1;
                    state <= WAIT;


class agent extends uvm_agent;
    sequencer sqr;
    driver    drv;
    monitor   mon;

    uvm_analysis_port #(packet_in) item_collected_port;

    `uvm_component_utils(agent)

    function new(string name = "agent", uvm_component parent = null);
        super.new(name, parent);
        item_collected_port = new("item_collected_port", this);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mon = monitor::type_id::create("mon", this);
        sqr = sequencer::type_id::create("sqr", this);

    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        mon.item_collected_port.connect(item_collected_port);
        drv.seq_item_port.connect(sqr.seq_item_export);
    endfunction
endclass: agent

drv = driver::type_id::create("drv", this);


class agent_out extends uvm_agent;
    driver_out    drv;
    monitor_out   mon;

    uvm_analysis_port #(packet_out) item_collected_port;

    `uvm_component_utils(agent_out)

        super.new(name, parent);
        item_collected_port = new("item_collected_port", this);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mon = monitor_out::type_id::create("mon_out", this);
        drv = driver_out::type_id::create("drv_out", this);
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        mon.item_collected_port.connect(item_collected_port);
    endfunction
endclass: agent_out

function new(string name = "agent_out", uvm_component parent = null);


class comparator #(type T = packet_out) extends uvm_scoreboard;
  typedef comparator #(T) this_type;
  `uvm_component_param_utils(this_type)

  const static string type_name = "comparator #(T)";

  uvm_put_imp #(T, this_type) from_refmod;
  uvm_analysis_imp #(T, this_type) from_dut;

  typedef uvm_built_in_converter #( T ) convert;

  int m_matches, m_mismatches;
  T exp;
  bit free;
  event compared, end_of_simulation;

  function new(string name, uvm_component parent);
    super.new(name, parent);
    from_refmod = new("from_refmod", this);
    from_dut = new("from_dut", this);
    m_matches = 0;
    m_mismatches = 0;
    exp = new("exp");
    free = 1;
  endfunction

  virtual function string get_type_name();
    return type_name;
  endfunction

  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    @(end_of_simulation);
    phase.drop_objection(this);
  endtask

  virtual task put(T t);
    if(!free) @compared;
    exp.copy(t);
    free = 0;

    @compared;
    free = 1;
  endtask

  virtual function bit try_put(T t);
    if(free) begin
      exp.copy(t);
      free = 0;
      return 1;
    end
    else return 0;
  endfunction

  virtual function bit can_put();
    return free;
  endfunction

  virtual function void write(T rec);
    if (free)

    if(!(exp.compare(rec))) begin
      uvm_report_warning("Comparator Mismatch", "");
      m_mismatches++;
    end
    else begin
      uvm_report_info("Comparator Match", "");
      m_matches++;
    end

    if(m_matches+m_mismatches > 100)
      -> end_of_simulation;

    -> compared;
  endfunction
endclass

uvm_report_fatal("No expect transaction to compare with", "");


typedef virtual input_if input_vif;

class driver extends uvm_driver #(packet_in);
    `uvm_component_utils(driver)
    input_vif vif;
    event begin_record, end_record;

    function new(string name = "driver", uvm_component parent = null);
        super.new(name, parent);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);

    endfunction

    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);
        fork
            reset_signals();
            get_and_drive(phase);
            record_tr();
        join
    endtask

    virtual protected task reset_signals();
        wait (vif.rst === 1);
        forever begin
            vif.valid <= '0;
            vif.A <= 'x;
            vif.B <= 'x;
            @(posedge vif.rst);
        end
    endtask

    virtual protected task get_and_drive(uvm_phase phase);
        wait(vif.rst === 1);
        @(negedge vif.rst);
        @(posedge vif.clk);

        forever begin
            seq_item_port.get(req);
            -> begin_record;
            drive_transfer(req);
        end
    endtask

    virtual protected task drive_transfer(packet_in tr);
        vif.A = tr.A;
        vif.B = tr.B;
        vif.valid = 1;

        @(posedge vif.clk)

        while(!vif.ready)
            @(posedge vif.clk);

        -> end_record;
        @(posedge vif.clk); //hold time
        vif.valid = 0;
        @(posedge vif.clk);
    endtask

    virtual task record_tr();
        forever begin
            @(begin_record);
            begin_tr(req, "driver");
            @(end_record);
            end_tr(req);
        end
    endtask
endclass

assert(uvm_config_db#(input_vif)::get(this, "", "vif", vif));


typedef virtual output_if output_vif;

class driver_out extends uvm_driver #(packet_out);
    `uvm_component_utils(driver_out)
    output_vif vif;

    function new(string name = "driver_out", uvm_component parent = null);
        super.new(name, parent);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);

    endfunction

    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);
        fork
            reset_signals();
            drive(phase);
        join
    endtask

    virtual protected task reset_signals();
        wait (vif.rst === 1);
        forever begin
            vif.ready <= '0;
            @(posedge vif.rst);
        end
    endtask

    virtual protected task drive(uvm_phase phase);
        wait(vif.rst === 1);
        @(negedge vif.rst);
        forever begin
          @(posedge vif.clk);
          vif.ready <= 1;
        end
    endtask
endclass

assert(uvm_config_db#(output_vif)::get(this, "", "vif", vif));


class env extends uvm_env;
    agent       mst;
    refmod      rfm;
    agent_out   slv;
    comparator #(packet_out) comp;
    uvm_tlm_analysis_fifo #(packet_in) to_refmod;

    `uvm_component_utils(env)

    function new(string name, uvm_component parent = null);
        super.new(name, parent);
        to_refmod = new("to_refmod", this);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mst = agent::type_id::create("mst", this);
        slv = agent_out::type_id::create("slv", this);
        rfm = refmod::type_id::create("rfm", this);
        comp = comparator#(packet_out)::type_id::create("comp", this);
    endfunction

    virtual function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        // Connect MST to FIFO
        mst.item_collected_port.connect(to_refmod.analysis_export);

        // Connect FIFO to REFMOD

        //Connect scoreboard
        rfm.out.connect(comp.from_refmod);
        slv.item_collected_port.connect(comp.from_dut);
    endfunction

    virtual function void end_of_elaboration_phase(uvm_phase phase);
        super.end_of_elaboration_phase(phase);
    endfunction

    virtual function void report_phase(uvm_phase phase);
        super.report_phase(phase);
        `uvm_info(get_type_name(), $sformatf("Reporting matched %0d", comp.m_matches), UVM_NONE)
        if (comp.m_mismatches) begin
            `uvm_error(get_type_name(), $sformatf("Saw %0d mismatched samples", comp.m_mismatches))
        end
    endfunction
endclass


interface input_if(input clk, rst);
    logic [31:0] A, B;
    logic valid, ready;

modport port(input clk, rst, A, B, valid, output ready);


class monitor extends uvm_monitor;
    input_vif  vif;
    event begin_record, end_record;
    packet_in tr;
    uvm_analysis_port #(packet_in) item_collected_port;
    `uvm_component_utils(monitor)

        super.new(name, parent);
        item_collected_port = new ("item_collected_port", this);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        assert(uvm_config_db#(input_vif)::get(this, "", "vif", vif));
        tr = packet_in::type_id::create("tr", this);
    endfunction

    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);
        fork
            collect_transactions(phase);
            record_tr();
        join
    endtask

    virtual task collect_transactions(uvm_phase phase);
        wait(vif.rst === 1);
        @(negedge vif.rst);

        forever begin
            do begin
                @(posedge vif.clk);
            end while (vif.valid === 0 || vif.ready === 0);
            -> begin_record;

            tr.A = vif.A;
            tr.B = vif.B;
            item_collected_port.write(tr);

            @(posedge vif.clk);
            -> end_record;
        end
    endtask

    virtual task record_tr();
        forever begin
            @(begin_record);
            begin_tr(tr, "monitor");
            @(end_record);
            end_tr(tr);
        end
    endtask
endclass

function new(string name, uvm_component parent);


class monitor_out extends uvm_monitor;
    `uvm_component_utils(monitor_out)

    event begin_record, end_record;
    packet_out tr;
    uvm_analysis_port #(packet_out) item_collected_port;

    function new(string name, uvm_component parent);
        super.new(name, parent);
        item_collected_port = new ("item_collected_port", this);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        assert(uvm_config_db#(output_vif)::get(this, "", "vif", vif));
        tr = packet_out::type_id::create("tr", this);
    endfunction

    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);
        fork
            collect_transactions(phase);
            record_tr();
        join
    endtask

    virtual task collect_transactions(uvm_phase phase);
        wait(vif.rst === 1);
        @(negedge vif.rst);

        forever begin
            do begin
                @(posedge vif.clk);
            end while (vif.valid === 0 || vif.ready === 0);
            -> begin_record;

            tr.data = vif.data;
            item_collected_port.write(tr);

            @(posedge vif.clk);
            -> end_record;
        end
    endtask

    virtual task record_tr();
        forever begin
            @(begin_record);
            begin_tr(tr, "monitor_out");
            @(end_record);
            end_tr(tr);
        end
    endtask
endclass


    logic [31:0] data;
    logic valid, ready;

    modport port(input clk, rst, output valid, data, ready);
endinterface


class packet_in extends uvm_sequence_item;
    rand integer A;
    rand integer B;

    `uvm_object_utils_begin(packet_in)
        `uvm_field_int(A, UVM_ALL_ON|UVM_HEX)

    `uvm_object_utils_end

    function new(string name="packet_in");
        super.new(name);
    endfunction: new
endclass: packet_in


class packet_out extends uvm_sequence_item;
    integer data;

    `uvm_object_utils_begin(packet_out)
        `uvm_field_int(data, UVM_ALL_ON|UVM_HEX)
    `uvm_object_utils_end

        super.new(name);
    endfunction: new
endclass: packet_out


import "DPI-C" context function int sum(int a, int b);

class refmod extends uvm_component;
    `uvm_component_utils(refmod)

    packet_in tr_in;
    packet_out tr_out;
    integer a, b;
    uvm_get_port #(packet_in) in;
    uvm_put_port #(packet_out) out;

    function new(string name = "refmod", uvm_component parent);
        super.new(name, parent);
        in = new("in", this);
        out = new("out", this);
    endfunction

    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);

    endfunction: build_phase

    virtual task run_phase(uvm_phase phase);
        super.run_phase(phase);

        forever begin
            in.get(tr_in);
            tr_out.data = sum(tr_in.A, tr_in.B);
            out.put(tr_out);
        end
    endtask: run_phase
endclass: refmod

tr_out = packet_out::type_id::create("tr_out", this);


class sequence_in extends uvm_sequence #(packet_in);
    `uvm_object_utils(sequence_in)

    function new(string name="sequence_in");
        super.new(name);
    endfunction: new

    task body;
        packet_in tx;

        forever begin
            tx = packet_in::type_id::create("tx");
            start_item(tx);

            finish_item(tx);
        end
    endtask: body
endclass: sequence_in


    `uvm_component_utils(sequencer)

    function new (string name = "sequencer", uvm_component parent = null);
        super.new(name, parent);
    endfunction
endclass: sequencer

class sequencer extends uvm_sequencer #(packet_in);


class simple_test extends uvm_test;
  env env_h;

  `uvm_component_utils(simple_test)

  function new(string name, uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env_h = env::type_id::create("env_h", this);
    seq = sequence_in::type_id::create("seq", this);
  endfunction

  task run_phase(uvm_phase phase);
    seq.start(env_h.mst.sqr);
  endtask: run_phase

endclass


import uvm_pkg::*;
`include "uvm_macros.svh"
`include "./input_if.sv"
`include "./output_if.sv"
`include "./adder.sv"
`include "./packet_in.sv"
`include "./packet_out.sv"
`include "./sequence_in.sv"
`include "./sequencer.sv"
`include "./driver.sv"
`include "./driver_out.sv"
`include "./monitor.sv"
`include "./monitor_out.sv"
`include "./agent.sv"
`include "./agent_out.sv"
`include "./refmod.sv"
`include "./comparator.sv"
`include "./env.sv"
`include "./simple_test.sv"

//Top
module top;
  logic clk;
  logic rst;

  initial begin
    clk = 0;
    rst = 1;
    #22 rst = 0;

  end

  always #5 clk = !clk;

  input_if in(clk, rst);
  output_if out(clk, rst);

  adder sum(in, out, state);

  initial begin
    `ifdef INCA
       $recordvars();
    `endif
    `ifdef VCS
       $vcdpluson;
    `endif
    `ifdef QUESTA
       $wlfdumpvars();
       set_config_int("*", "recording_detail", 1);
    `endif

    uvm_config_db#(input_vif)::set(uvm_root::get(), "*.env_h.mst.*", "vif", in);
    uvm_config_db#(output_vif)::set(uvm_root::get(), "*.env_h.slv.*",  "vif", out);

    run_test("simple_test");
  end
endmodule


`ifndef __SVREAL_SV__
`define __SVREAL_SV__

// include files for Berkeley HardFloat if needed
`ifdef HARD_FLOAT
    `include "HardFloat_consts.vi"

    `ifndef HARD_FLOAT_CONTROL
        `define HARD_FLOAT_CONTROL `flControl_tininessAfterRounding
    `endif
    `ifndef HARD_FLOAT_ROUNDING
        `define HARD_FLOAT_ROUNDING `round_near_even
    `endif
`endif

// fixed-point representation defaults
// (can override by defining them externally)

`ifndef SHORT_WIDTH_REAL
    `define SHORT_WIDTH_REAL 18
`endif

`ifndef LONG_WIDTH_REAL
    `define LONG_WIDTH_REAL 25
`endif

// configuration for Berkeley HardFloat, if used

`ifndef HARD_FLOAT_EXP_WIDTH
    `define HARD_FLOAT_EXP_WIDTH 8
`endif

`ifndef HARD_FLOAT_SIG_WIDTH
    `define HARD_FLOAT_SIG_WIDTH 23
`endif

`define HARD_FLOAT_WIDTH (1+(`HARD_FLOAT_EXP_WIDTH)+(`HARD_FLOAT_SIG_WIDTH))

`define HARD_FLOAT_SIGN_BIT ((`HARD_FLOAT_SIG_WIDTH)+(`HARD_FLOAT_EXP_WIDTH))

// declare a more generic version of $clog2 that supports
// real number inputs, which is needed to automatically
// compute exponents.  the the value returned is
// int(ceil(log2(x)))
function int clog2_math(input real x);
    clog2_math = 0;
    if (x > 0) begin
        while (x < (2.0**(clog2_math))) begin
            clog2_math = clog2_math - 1;
        end
        while (x > (2.0**(clog2_math))) begin
            clog2_math = clog2_math + 1;
        end
    end
endfunction

`define CALC_EXP(range, width) (clog2_math((real'(range))/((2.0**((width)-1))-1.0)))

`define MAX_MATH(a, b) \
    (((a) > (b)) ? (a) : (b))

`define MIN_MATH(a, b) \
    (((a) < (b)) ? (a) : (b))

`define ABS_MATH(a) \
    (((a) > 0) ? (a) : (-(a)))

`define FIXED_TO_FLOAT(significand, exponent) \
    ((significand)*(2.0**(exponent)))

`define FLOAT_TO_FIXED(value, exponent) \
    ((real'(value))*(2.0**(-(exponent))))

// convert the HardFloat recoded format to a real number
function real recfn2real(input logic [((`HARD_FLOAT_EXP_WIDTH)+(`HARD_FLOAT_SIG_WIDTH)):0] in);
    // recoded format
    logic rec_sign;
    logic [(`HARD_FLOAT_EXP_WIDTH):0] rec_exp;
    logic [((`HARD_FLOAT_SIG_WIDTH)-2):0] rec_sig;
    logic [2:0] rec_exp_top;

    // double-precision format
    logic dbl_sign;
    logic [10:0] dbl_exp;
    logic [51:0] dbl_sig;
    logic [63:0] dbl_bits;

    // deconstruct input
    rec_sign = in[`HARD_FLOAT_SIGN_BIT];
    rec_exp = in[((`HARD_FLOAT_SIGN_BIT)-1):((`HARD_FLOAT_SIGN_BIT)-1-((`HARD_FLOAT_EXP_WIDTH)+1)+1)];
    rec_sig = in[((`HARD_FLOAT_SIG_WIDTH)-2):0];
    rec_exp_top = rec_exp[(`HARD_FLOAT_EXP_WIDTH):((`HARD_FLOAT_EXP_WIDTH)-3+1)];

    // walk through various cases
    if (rec_exp_top == 3'b000) begin
        // zero
        dbl_sign = rec_sign;
        dbl_exp = 0;
        dbl_sig = 0;
    end else if (rec_exp_top == 3'b110) begin
        // infinities
        dbl_sign = rec_sign;
        dbl_exp = '1;
        dbl_sig = '0;
    end else if (rec_exp_top == 3'b111) begin
        // NaNs
        dbl_sign = rec_sign;
        dbl_exp = '1;
        dbl_sig = '1;
    end else if (rec_exp < ((2**((`HARD_FLOAT_EXP_WIDTH)-1))+2)) begin
        // TODO: implement subnormal (treated as zero for now)
        dbl_sign = rec_sign;
        dbl_exp = 0;
        dbl_sig = 0;
    end else begin
        // normal
        dbl_sign = rec_sign;
        dbl_exp = rec_exp
                  - ((2**((`HARD_FLOAT_EXP_WIDTH)-1))+1)    // remove recoding offset
                  - ((2**((`HARD_FLOAT_EXP_WIDTH)-1))-1)    // remove exponent bias
                  + 1023;                                   // apply exponent bias
        if (((`HARD_FLOAT_SIG_WIDTH)-1) < 52) begin
            // zero-pad
            dbl_sig = rec_sig << (52-((`HARD_FLOAT_SIG_WIDTH)-1));
        end else begin
            // truncate
            dbl_sig = rec_sig >> (((`HARD_FLOAT_SIG_WIDTH)-1)-52);
        end
    end

    // assign the output
    dbl_bits = {dbl_sign, dbl_exp, dbl_sig};
    recfn2real = $bitstoreal(dbl_bits);
endfunction

`define REC_FN_TO_REAL(value) recfn2real(value)

// convert a real number to the HardFloat recoded format
function logic [((`HARD_FLOAT_EXP_WIDTH)+(`HARD_FLOAT_SIG_WIDTH)):0] real2recfn(input real in);
    // double-precision format
    logic dbl_sign;
    logic [10:0] dbl_exp;
    logic [51:0] dbl_sig;
    logic [63:0] dbl_bits;

    // recoded format
    logic rec_sign;
    int rec_exp_int;
    logic [(`HARD_FLOAT_EXP_WIDTH):0] rec_exp;
    logic [((`HARD_FLOAT_SIG_WIDTH)-2):0] rec_sig;

    // deconstruct input
    dbl_bits = $realtobits(in);
    dbl_sign = dbl_bits[63];
    dbl_exp = dbl_bits[62:52];
    dbl_sig = dbl_bits[51:0];

    if (dbl_exp == 0) begin
        // zero or subnormal
        // TODO: handle subnormal properly
        rec_sign = dbl_sign;
        rec_exp = '0;
        rec_sig = '0;
    end else if (dbl_exp == 11'h7FF) begin
        if (dbl_sig == 0) begin
            // infinities
            rec_sign = dbl_sign;
            rec_exp = {3'b110, {((`HARD_FLOAT_EXP_WIDTH)-2){1'b0}}};
            rec_sig = '0;
        end else begin
            // NaNs
            rec_sign = dbl_sign;
            rec_exp = {3'b111, {((`HARD_FLOAT_EXP_WIDTH)-2){1'b0}}};
            rec_sig = '0;
        end
    end else begin
        // normal
        rec_sign = dbl_sign;
        rec_exp_int = dbl_exp
                      - 1023                                     // remove exponent bias
                      + ((2**((`HARD_FLOAT_EXP_WIDTH)-1))-1)     // apply exponent bias
                      + ((2**((`HARD_FLOAT_EXP_WIDTH)-1))+1);    // apply recoding bias
        if (rec_exp_int < ((2**((`HARD_FLOAT_EXP_WIDTH)-1))+2)) begin
            // TODO: handle case where input is normal but output is subnormal
            // for now the output is simply zero
            rec_exp = '0;
            rec_sig = '0;
        end else if (rec_exp_int > ((3*(2**((`HARD_FLOAT_EXP_WIDTH)-1)))-1)) begin
            // Exponent is too large to be represented, so treat as an infinity
            rec_exp = {3'b110, {((`HARD_FLOAT_EXP_WIDTH)-2){1'b0}}};
            rec_sig = '0;
        end else begin
            rec_exp = rec_exp_int;
            if (((`HARD_FLOAT_SIG_WIDTH)-1) > 52) begin
                // zero-pad (lossless)
                rec_sig = dbl_sig << (((`HARD_FLOAT_SIG_WIDTH)-1)-52);
            end else begin
                // truncate (lossy)
                rec_sig = dbl_sig >> (52-((`HARD_FLOAT_SIG_WIDTH)-1));
            end
        end
    end

    // assign the output
    real2recfn = {rec_sign, rec_exp, rec_sig};
endfunction

`define REAL_TO_REC_FN(value) real2recfn(value)

// real number parameters
// width and exponent are only used for the fixed-point
// representation

`define RANGE_PARAM_REAL(name) ``name``_range_val

`define WIDTH_PARAM_REAL(name) ``name``_width_val

`define EXPONENT_PARAM_REAL(name) ``name``_exponent_val

`define PRINT_FORMAT_REAL(name) \
    $display(`"``name``: {width=%0d, exponent=%0d, range=%0f}`", `WIDTH_PARAM_REAL(name), `EXPONENT_PARAM_REAL(name), `RANGE_PARAM_REAL(name))

// real number representation type

`define DATA_TYPE_REAL(width_expr) \
    `ifdef FLOAT_REAL \
        real \
    `elsif HARD_FLOAT \
        logic [(`HARD_FLOAT_SIGN_BIT):0] \
    `else \
        logic signed [((width_expr)-1):0] \
    `endif

// module ports

`define DECL_REAL(port) \
    parameter real `RANGE_PARAM_REAL(port) = 0, \
    parameter integer `WIDTH_PARAM_REAL(port) = 0, \
    parameter integer `EXPONENT_PARAM_REAL(port) = 0

`define PASS_REAL(port, name) \
    .`RANGE_PARAM_REAL(port)(`RANGE_PARAM_REAL(name)), \
    .`WIDTH_PARAM_REAL(port)(`WIDTH_PARAM_REAL(name)), \
    .`EXPONENT_PARAM_REAL(port)(`EXPONENT_PARAM_REAL(name))

`define PORT_REAL(port) \
    `ifdef FLOAT_REAL \
        `DATA_TYPE_REAL(`WIDTH_PARAM_REAL(port)) port \
    `else \
        wire `DATA_TYPE_REAL(`WIDTH_PARAM_REAL(port)) port \
    `endif

`define INPUT_REAL(port) input `PORT_REAL(port)

`define OUTPUT_REAL(port) output `PORT_REAL(port)

// Displaying real number signals

`define TO_REAL(name) \
    `ifdef FLOAT_REAL \
        (name) \
    `elsif HARD_FLOAT \
        (`REC_FN_TO_REAL(name)) \
    `else \
		(`FIXED_TO_FLOAT((name), (`EXPONENT_PARAM_REAL(name)))) \
    `endif

`define PRINT_REAL(name) \
    $display(`"``name``=%0f`", `TO_REAL(name))

// force a real number

`define FROM_REAL(expr, name) \
    `ifdef FLOAT_REAL \
        (expr) \
    `elsif HARD_FLOAT \
        (`REAL_TO_REC_FN(expr)) \
    `else \
		(`FLOAT_TO_FIXED((expr), (`EXPONENT_PARAM_REAL(name)))) \
    `endif

`define FORCE_REAL(expr, name) \
    name = `FROM_REAL(expr, name)

// assert that real number is within specified range

`define ASSERTION_REAL(in_name) \
    assertion_real #( \
        `PASS_REAL(in, in_name), \
        .name(`"``in_name```") \
    ) assertion_real_``in_name``_i ( \
        .in(in_name) \
    )

// creating real numbers
// the data type declaration comes first so that directives like mark_debug
// and dont_touch can be used

`define MAKE_FORMAT_REAL(name, range_expr, width_expr, exponent_expr) \
    `DATA_TYPE_REAL(width_expr) name; \
    localparam real `RANGE_PARAM_REAL(name) = range_expr; \
    localparam integer `WIDTH_PARAM_REAL(name) = width_expr; \
    localparam integer `EXPONENT_PARAM_REAL(name) = exponent_expr \
    `ifdef RANGE_ASSERTIONS \
        ; `ASSERTION_REAL(name) \
    `endif

`define REAL_FROM_WIDTH_EXP(name, width_expr, exponent_expr) \
    `MAKE_FORMAT_REAL(name, 2.0**((width_expr)+(exponent_expr)-1), width_expr, exponent_expr)

// copying real number format

`define GET_FORMAT_REAL(in_name) \
    `DATA_TYPE_REAL(`WIDTH_PARAM_REAL(in_name))

`define COPY_FORMAT_REAL(in_name, out_name) \
    `MAKE_FORMAT_REAL(out_name, `RANGE_PARAM_REAL(in_name), `WIDTH_PARAM_REAL(in_name), `EXPONENT_PARAM_REAL(in_name))

// negation
// note that since the range of a fixed-point number is defined as +/- |range|, the negation of
// the fixed point numbers can always be represented in the original format.

`define NEGATE_INTO_REAL(in_name, out_name) \
    negate_real #( \
        `PASS_REAL(in, in_name), \
        `PASS_REAL(out, out_name) \
    ) negate_real_``out_name``_i ( \
        .in(in_name), \
        .out(out_name) \
    )

`define NEGATE_REAL(in_name, out_name) \
    `COPY_FORMAT_REAL(in_name, out_name); \
    `NEGATE_INTO_REAL(in_name, out_name)

// absolute value

`define ABS_INTO_REAL(in_name, out_name) \
    abs_real #( \
        `PASS_REAL(in, in_name), \
        `PASS_REAL(out, out_name) \
    ) abs_real_``out_name``_i ( \
        .in(in_name), \
        .out(out_name) \
    )

`define ABS_REAL(in_name, out_name) \
    `COPY_FORMAT_REAL(in_name, out_name); \
    `ABS_INTO_REAL(in_name, out_name)

// construct real number from range
// the following four macros depend on clog2_math

`define MAKE_GENERIC_REAL(name, range_expr, width_expr) \
    `MAKE_FORMAT_REAL(name, range_expr, width_expr, `CALC_EXP(range_expr, width_expr))

`define MAKE_SHORT_REAL(name, range_expr) \
    `MAKE_GENERIC_REAL(name, range_expr, `SHORT_WIDTH_REAL)

`define MAKE_LONG_REAL(name, range_expr) \
    `MAKE_GENERIC_REAL(name, range_expr, `LONG_WIDTH_REAL)

`define MAKE_REAL(name, range_expr) \
    `MAKE_LONG_REAL(name, range_expr)

// assigning real numbers
// note that the negative version of each number will already have be assigned when
// out_name was defined

`define ASSIGN_REAL(in_name, out_name) \
    assign_real #( \
        `PASS_REAL(in, in_name), \
        `PASS_REAL(out, out_name) \
    ) assign_real_``out_name``_i ( \
        .in(in_name), \
        .out(out_name) \
    )

// real constants
// range is skewed just a bit higher to make sure that the
// fixed-point representation falls within the range

`define ASSIGN_CONST_REAL(const_expr, name) \
    assign name = `FROM_REAL(const_expr, name)

`define CONST_RANGE_REAL(const_expr) \
    (1.01*`ABS_MATH(const_expr))

`define MAKE_GENERIC_CONST_REAL(const_expr, name, width_expr) \
    `MAKE_GENERIC_REAL(name, `CONST_RANGE_REAL(const_expr), width_expr); \
    `ASSIGN_CONST_REAL(const_expr, name)

`define MAKE_SHORT_CONST_REAL(const_expr, name) \
    `MAKE_GENERIC_CONST_REAL(const_expr, name, `SHORT_WIDTH_REAL)

`define MAKE_LONG_CONST_REAL(const_expr, name) \
    `MAKE_GENERIC_CONST_REAL(const_expr, name, `LONG_WIDTH_REAL)

`define MAKE_CONST_REAL(const_expr, name) \
    `MAKE_LONG_CONST_REAL(const_expr, name)

// multiplication of two variables

`define MUL_INTO_REAL(a_name, b_name, c_name) \
    mul_real #( \
        `PASS_REAL(a, a_name), \
        `PASS_REAL(b, b_name), \
        `PASS_REAL(c, c_name) \
    ) mul_real_``c_name``_i ( \
        .a(a_name), \
        .b(b_name), \
        .c(c_name) \
    )

`define MUL_REAL_GENERIC(a_name, b_name, c_name, c_width) \
    `MAKE_GENERIC_REAL(c_name, `RANGE_PARAM_REAL(a_name)*`RANGE_PARAM_REAL(b_name), c_width); \
    `MUL_INTO_REAL(a_name, b_name, c_name)

`define MUL_REAL(a_name, b_name, c_name) \
    `MUL_REAL_GENERIC(a_name, b_name, c_name, `LONG_WIDTH_REAL)

// multiplication of a constant and variable

`define MUL_CONST_INTO_REAL_GENERIC(const_expr, in_name, out_name, const_width) \
    `MAKE_GENERIC_CONST_REAL(const_expr, zzz_tmp_``out_name``, const_width); \
    `MUL_INTO_REAL(zzz_tmp_``out_name``, in_name, out_name)

`define MUL_CONST_INTO_REAL(const_expr, in_name, out_name) \
    `MUL_CONST_INTO_REAL_GENERIC(const_expr, in_name, out_name, `SHORT_WIDTH_REAL)

`define MUL_CONST_REAL_GENERIC(const_expr, in_name, out_name, const_width, out_width) \
    `MAKE_GENERIC_REAL(out_name, `CONST_RANGE_REAL(const_expr)*`RANGE_PARAM_REAL(in_name), out_width); \
    `MUL_CONST_INTO_REAL_GENERIC(const_expr, in_name, out_name, const_width)

`define MUL_CONST_REAL(const_expr, in_name, out_name) \
    `MUL_CONST_REAL_GENERIC(const_expr, in_name, out_name, `SHORT_WIDTH_REAL, `LONG_WIDTH_REAL)

// generic addition or subtraction

`define ADD_SUB_INTO_REAL(opcode_value, a_name, b_name, c_name) \
    add_sub_real #( \
        `PASS_REAL(a, a_name), \
        `PASS_REAL(b, b_name), \
        `PASS_REAL(c, c_name), \
		.opcode(opcode_value) \
    ) add_sub_real_``c_name``_i ( \
        .a(a_name), \
        .b(b_name), \
        .c(c_name) \
    )

// addition of two variables

`define ADD_OPCODE_REAL 0

`define ADD_INTO_REAL(a_name, b_name, c_name) \
    `ADD_SUB_INTO_REAL(`ADD_OPCODE_REAL, a_name, b_name, c_name)

`define ADD_REAL_GENERIC(a_name, b_name, c_name, c_width) \
    `MAKE_GENERIC_REAL(c_name, `RANGE_PARAM_REAL(a_name) + `RANGE_PARAM_REAL(b_name), c_width); \
    `ADD_INTO_REAL(a_name, b_name, c_name)

`define ADD_REAL(a_name, b_name, c_name) \
    `ADD_REAL_GENERIC(a_name, b_name, c_name, `LONG_WIDTH_REAL)

// addition of a constant and a variable

`define ADD_CONST_INTO_REAL_GENERIC(const_expr, in_name, out_name, const_width) \
    `MAKE_GENERIC_CONST_REAL(const_expr, zzz_tmp_``out_name``, const_width); \
    `ADD_INTO_REAL(zzz_tmp_``out_name``, in_name, out_name)

`define ADD_CONST_INTO_REAL(const_expr, in_name, out_name) \
    `ADD_CONST_INTO_REAL_GENERIC(const_expr, in_name, out_name, `LONG_WIDTH_REAL)

`define ADD_CONST_REAL_GENERIC(const_expr, in_name, out_name, const_width, out_width) \
    `MAKE_GENERIC_REAL(out_name, `CONST_RANGE_REAL(const_expr) + `RANGE_PARAM_REAL(in_name), out_width); \
    `ADD_CONST_INTO_REAL_GENERIC(const_expr, in_name, out_name, const_width)

`define ADD_CONST_REAL(const_expr, in_name, out_name) \
    `ADD_CONST_REAL_GENERIC(const_expr, in_name, out_name, `LONG_WIDTH_REAL, `LONG_WIDTH_REAL)

// addition of three variables

`define ADD3_INTO_REAL_GENERIC(a_name, b_name, c_name, d_name, tmp_width) \
    `ADD_REAL_GENERIC(a_name, b_name, zzz_tmp_``d_name``, tmp_width); \
    `ADD_INTO_REAL(zzz_tmp_``d_name``, c_name, d_name)

`define ADD3_INTO_REAL(a_name, b_name, c_name, d_name) \
    `ADD3_INTO_REAL_GENERIC(a_name, b_name, c_name, d_name, `LONG_WIDTH_REAL)

`define ADD3_REAL_GENERIC(a_name, b_name, c_name, d_name, tmp_width, d_width) \
    `MAKE_GENERIC_REAL(d_name, `RANGE_PARAM_REAL(a_name) + `RANGE_PARAM_REAL(b_name) + `RANGE_PARAM_REAL(c_name), d_width); \
    `ADD3_INTO_REAL_GENERIC(a_name, b_name, c_name, d_name, tmp_width)

`define ADD3_REAL(a_name, b_name, c_name, d_name) \
    `ADD3_REAL_GENERIC(a_name, b_name, c_name, d_name, `LONG_WIDTH_REAL, `LONG_WIDTH_REAL)

// subtraction of two variables

`define SUB_OPCODE_REAL 1

`define SUB_INTO_REAL(a_name, b_name, c_name) \
    `ADD_SUB_INTO_REAL(`SUB_OPCODE_REAL, a_name, b_name, c_name)

`define SUB_REAL_GENERIC(a_name, b_name, c_name, c_width) \
    `MAKE_GENERIC_REAL(c_name, `RANGE_PARAM_REAL(a_name) + `RANGE_PARAM_REAL(b_name), c_width); \
    `SUB_INTO_REAL(a_name, b_name, c_name)

`define SUB_REAL(a_name, b_name, c_name) \
    `SUB_REAL_GENERIC(a_name, b_name, c_name, `LONG_WIDTH_REAL)

// conditional assignment

`define ITE_INTO_REAL(cond_name, true_name, false_name, out_name) \
    ite_real #( \
        `PASS_REAL(true, true_name), \
        `PASS_REAL(false, false_name), \
        `PASS_REAL(out, out_name) \
    ) ite_real_``out_name``_i ( \
        .cond(cond_name), \
        .true(true_name), \
        .false(false_name), \
        .out(out_name) \
    )

`define ITE_REAL_GENERIC(cond_name, true_name, false_name, out_name, out_width) \
    `MAKE_GENERIC_REAL(out_name, `MAX_MATH(`RANGE_PARAM_REAL(true_name), `RANGE_PARAM_REAL(false_name)), out_width); \
    `ITE_INTO_REAL(cond_name, true_name, false_name, out_name)

`define ITE_REAL(cond_name, true_name, false_name, out_name) \
    `ITE_REAL_GENERIC(cond_name, true_name, false_name, out_name, `LONG_WIDTH_REAL) \

// generic comparison

`define COMP_INTO_REAL(opcode_value, a_name, b_name, c_name) \
    comp_real #( \
        `PASS_REAL(a, a_name), \
        `PASS_REAL(b, b_name), \
        .opcode(opcode_value) \
    ) comp_real_``c_name``_i ( \
        .a(a_name), \
        .b(b_name), \
        .c(c_name) \
    )

// greater than

`define GT_OPCODE_REAL 0

`define GT_INTO_REAL(a_name, b_name, c_name) \
    `COMP_INTO_REAL(`GT_OPCODE_REAL, a_name, b_name, c_name)

`define GT_REAL(a_name, b_name, c_name) \
    logic c_name; \
    `GT_INTO_REAL(a_name, b_name, c_name)

// greater than or equal to

`define GE_OPCODE_REAL 1

`define GE_INTO_REAL(a_name, b_name, c_name) \
    `COMP_INTO_REAL(`GE_OPCODE_REAL, a_name, b_name, c_name)

`define GE_REAL(a_name, b_name, c_name) \
    logic c_name; \
    `GE_INTO_REAL(a_name, b_name, c_name)

// less than

`define LT_OPCODE_REAL 2

`define LT_INTO_REAL(a_name, b_name, c_name) \
    `COMP_INTO_REAL(`LT_OPCODE_REAL, a_name, b_name, c_name)

`define LT_REAL(a_name, b_name, c_name) \
    logic c_name; \
    `LT_INTO_REAL(a_name, b_name, c_name)

// less than or equal to

`define LE_OPCODE_REAL 3

`define LE_INTO_REAL(a_name, b_name, c_name) \
    `COMP_INTO_REAL(`LE_OPCODE_REAL, a_name, b_name, c_name)

`define LE_REAL(a_name, b_name, c_name) \
    logic c_name; \
    `LE_INTO_REAL(a_name, b_name, c_name)

// equal to

`define EQ_OPCODE_REAL 4

`define EQ_INTO_REAL(a_name, b_name, c_name) \
    `COMP_INTO_REAL(`EQ_OPCODE_REAL, a_name, b_name, c_name)

`define EQ_REAL(a_name, b_name, c_name) \
    logic c_name; \
    `EQ_INTO_REAL(a_name, b_name, c_name)

// not equal to

`define NE_OPCODE_REAL 5

`define NE_INTO_REAL(a_name, b_name, c_name) \
    `COMP_INTO_REAL(`NE_OPCODE_REAL, a_name, b_name, c_name)

`define NE_REAL(a_name, b_name, c_name) \
    logic c_name; \
    `NE_INTO_REAL(a_name, b_name, c_name)

// max of two variables

`define MAX_INTO_REAL(a_name, b_name, c_name) \
    `GT_REAL(a_name, b_name, zzz_tmp_``c_name``); \
    `ITE_INTO_REAL(zzz_tmp_``c_name``, a_name, b_name, c_name)

`define MAX_REAL_GENERIC(a_name, b_name, c_name, c_width) \
    `MAKE_GENERIC_REAL(c_name, `MAX_MATH(`RANGE_PARAM_REAL(a_name), `RANGE_PARAM_REAL(b_name)), c_width); \
    `MAX_INTO_REAL(a_name, b_name, c_name)

`define MAX_REAL(a_name, b_name, c_name) \
    `MAX_REAL_GENERIC(a_name, b_name, c_name, `LONG_WIDTH_REAL)

// min of two variables

`define MIN_INTO_REAL(a_name, b_name, c_name) \
    `LT_REAL(a_name, b_name, zzz_tmp_``c_name``); \
    `ITE_INTO_REAL(zzz_tmp_``c_name``, a_name, b_name, c_name)

`define MIN_REAL_GENERIC(a_name, b_name, c_name, c_width) \
    `MAKE_GENERIC_REAL(c_name, `MAX_MATH(`RANGE_PARAM_REAL(a_name), `RANGE_PARAM_REAL(b_name)), c_width); \
    `MIN_INTO_REAL(a_name, b_name, c_name)

`define MIN_REAL(a_name, b_name, c_name) \
    `MIN_REAL_GENERIC(a_name, b_name, c_name, `LONG_WIDTH_REAL)

// conversion from real number to integer
// note that this always rounds down, regardless of whether HARD_FLOAT
// is used or not.

`define REAL_TO_INT(in_name, int_width_expr, out_name) \
    `ifdef FLOAT_REAL \
        logic signed[((int_width_expr)-1):0] out_name; \
        assign out_name = $floor(in_name) \
    `elsif HARD_FLOAT \
        logic signed[((int_width_expr)-1):0] out_name; \
        recFNToIN #( \
            .expWidth(`HARD_FLOAT_EXP_WIDTH), \
            .sigWidth(`HARD_FLOAT_SIG_WIDTH), \
            .intWidth(int_width_expr) \
        ) recFNToIN_``out_name``_i ( \
            .control(`HARD_FLOAT_CONTROL), \
            .in(in_name), \
            .roundingMode(`round_min), \
            .signedOut(1'b1), \
            .out(out_name), \
            .intExceptionFlags() \
        ) \
    `else \
        `REAL_FROM_WIDTH_EXP(out_name, int_width_expr, 0); \
        `ASSIGN_REAL(in_name, out_name) \
    `endif

`define REAL_INTO_INT(in_name, int_width_expr, out_name) \
    `REAL_TO_INT(in_name, int_width_expr, zzz_tmp_``out_name``); \
    assign out_name = zzz_tmp_``out_name``

// conversion from integer to real number

`define INT_TO_REAL(in_name, int_width_expr, out_name) \
    `REAL_FROM_WIDTH_EXP(out_name, int_width_expr, 0); \
    `ifdef FLOAT_REAL \
        assign out_name = 1.0*(in_name) \
    `elsif HARD_FLOAT \
        iNToRecFN #( \
            .intWidth(int_width_expr), \
            .expWidth(`HARD_FLOAT_EXP_WIDTH), \
            .sigWidth(`HARD_FLOAT_SIG_WIDTH) \
        ) iNToRecFN_``out_name``_i ( \
            .control(`HARD_FLOAT_CONTROL), \
            .signedIn(1'b1), \
            .in(in_name), \
            .roundingMode(`HARD_FLOAT_ROUNDING), \
            .out(out_name), \
            .exceptionFlags() \
        ) \
    `else \
        assign out_name = in_name \
    `endif

`define INT_INTO_REAL(in_name, int_width_expr, out_name) \
    `INT_TO_REAL(in_name, int_width_expr, zzz_tmp_``out_name``); \
    `ASSIGN_REAL(zzz_tmp_``out_name``, out_name)

// get the width of an integer

`define MEAS_UINT_WIDTH_INTO(in_name, in_width_expr, out_name, out_width_expr) \
    meas_uint_width #( \
        .in_width(in_width_expr), \
        .out_width(out_width_expr) \
    ) meas_uint_width_``out_name`` ( \
        .in(in_name), \
        .out(out_name) \
    )

`define MEAS_UINT_WIDTH(in_name, in_width_expr, out_name, out_width_expr) \
    logic [((out_width_expr)-1):0] out_name; \
    `MEAS_UINT_WIDTH_INTO(in_name, in_width_expr, out_name, out_width_expr)

// compressing an integer into a real number using an approximately logarithmic mapping

`define COMPRESS_UINT_INTO(in_name, in_width_expr, out_name) \
    compress_uint #( \
        .in_width(in_width_expr), \
        `PASS_REAL(out, out_name) \
    ) compress_uint_``out_name``_i ( \
        .in(in_name), \
        .out(out_name) \
    )

`define COMPRESS_UINT(in_name, in_width_expr, out_name) \
    `MAKE_REAL(out_name, ((in_width_expr)+1)); \
    `COMPRESS_UINT_INTO(in_name, in_width_expr, out_name)

// memory

`define DFF_INTO_REAL(d_name, q_name, rst_name, clk_name, cke_name, init_expr) \
    dff_real #( \
        `PASS_REAL(d, d_name), \
        `PASS_REAL(q, q_name), \
        .init(init_expr) \
    ) dff_real_``q_name``_i ( \
        .d(d_name), \
        .q(q_name), \
        .rst(rst_name), \
        .clk(clk_name), \
        .cke(cke_name) \
    )

`define DFF_REAL(d_name, q_name, rst_name, clk_name, cke_name, init_expr) \
    `COPY_FORMAT_REAL(d_name, q_name); \
    `DFF_INTO_REAL(d_name, q_name, rst_name, clk_name, cke_name, init_expr)

// synchronous ROM
// note that the data_bits_expr input is ignored when HARD_FLOAT is defined, because HARD_FLOAT
// signals always have width HARD_FLOAT_WIDTH.  this makes it easier to swap between default
// operation (fixed-point) and HARD_FLOAT

`define SYNC_ROM_INTO_REAL(addr_name, out_name, clk_name, ce_name, addr_bits_expr, data_bits_expr, file_path_expr, data_expt_expr) \
    sync_rom_real #( \
        `PASS_REAL(out, out_name), \
        .addr_bits(addr_bits_expr), \
        .data_bits( \
            `ifdef HARD_FLOAT \
                `HARD_FLOAT_WIDTH \
            `else \
                data_bits_expr \
            `endif \
        ), \
        .data_expt(data_expt_expr), \
        .file_path(file_path_expr) \
    ) sync_rom_real_``out_name``_i ( \
        .addr(addr_name), \
        .out(out_name), \
        .clk(clk_name), \
        .ce(ce_name) \
    )

`define SYNC_ROM_REAL(addr_name, out_name, clk_name, ce_name, addr_bits_expr, data_bits_expr, file_path_expr, data_expt_expr) \
    `REAL_FROM_WIDTH_EXP(out_name, data_bits_expr, data_expt_expr); \
    `SYNC_ROM_INTO_REAL(addr_name, out_name, clk_name, ce_name, addr_bits_expr, data_bits_expr, file_path_expr, data_expt_expr)

// synchronous RAM
// note that the data_bits_expr input is ignored when HARD_FLOAT is defined, because HARD_FLOAT
// signals always have width HARD_FLOAT_WIDTH.  this makes it easier to swap between default
// operation (fixed-point) and HARD_FLOAT

`define SYNC_RAM_INTO_REAL(addr_name, din_name, out_name, clk_name, ce_name, we_name, addr_bits_expr, data_bits_expr, data_expt_expr) \
    sync_ram_real #( \
        `PASS_REAL(out, out_name), \
        .addr_bits(addr_bits_expr), \
        .data_bits( \
            `ifdef HARD_FLOAT \
                `HARD_FLOAT_WIDTH \
            `else \
                data_bits_expr \
            `endif \
        ), \
        .data_expt(data_expt_expr) \
    ) sync_ram_real_``out_name``_i ( \
        .addr(addr_name), \
        .din(din_name), \
        .out(out_name), \
        .clk(clk_name), \
        .ce(ce_name), \
        .we(we_name) \
    )

`define SYNC_RAM_REAL(addr_name, din_name, out_name, clk_name, ce_name, we_name, addr_bits_expr, data_bits_expr, data_expt_expr) \
    `REAL_FROM_WIDTH_EXP(out_name, data_bits_expr, data_expt_expr); \
    `SYNC_RAM_INTO_REAL(addr_name, din_name, out_name, clk_name, ce_name, we_name, addr_bits_expr, data_bits_expr, data_expt_expr)

// synchronous RAM

// interface functions

// range is not included as a parameter since there is no
// way to read it out of the interface; as a result the
// maximum range for the width and exponent must be used

`define INTF_DECL_REAL(name) \
    parameter integer `WIDTH_PARAM_REAL(name)=0, \
    parameter integer `EXPONENT_PARAM_REAL(name)=0

`define REAL_INTF_PARAMS(name, width_expr, exponent_expr) \
    .`WIDTH_PARAM_REAL(name)(width_expr), \
    .`EXPONENT_PARAM_REAL(name)(exponent_expr)

`define INTF_FORMAT_REAL(name) ``name``_format_signal

 `define INTF_MAKE_REAL(name) \
     `DATA_TYPE_REAL(`WIDTH_PARAM_REAL(name)) name; \
     logic [((`WIDTH_PARAM_REAL(name))+(`EXPONENT_PARAM_REAL(name))-1):(`EXPONENT_PARAM_REAL(name))] `INTF_FORMAT_REAL(name)

`define INTF_WIDTH_REAL(name) ($size(`INTF_FORMAT_REAL(name)))

`define INTF_EXPONENT_REAL(name) ($low(`INTF_FORMAT_REAL(name)))

`define INTF_RANGE_REAL(name) (2.0**($high(`INTF_FORMAT_REAL(name))))

`define INTF_PASS_REAL(port, name) \
    .`WIDTH_PARAM_REAL(port)(`INTF_WIDTH_REAL(name)), \
    .`EXPONENT_PARAM_REAL(port)(`INTF_EXPONENT_REAL(name)), \
    .`RANGE_PARAM_REAL(port)(`INTF_RANGE_REAL(name))

`define INTF_ALIAS_REAL(path, name) \
    `MAKE_FORMAT_REAL(name, `INTF_RANGE_REAL(path), `INTF_WIDTH_REAL(path), `INTF_EXPONENT_REAL(path))

`define INTF_INPUT_TO_REAL(path, name) \
    `INTF_ALIAS_REAL(path, name); \
    assign name = path

`define INTF_OUTPUT_TO_REAL(path, name) \
    `INTF_ALIAS_REAL(path, name); \
    assign path = name

// modport-related functions

`define MODPORT_IN_REAL(name) \
    input name, \
    input `INTF_FORMAT_REAL(name)

`define MODPORT_OUT_REAL(name) \
    output name, \
    input `INTF_FORMAT_REAL(name)

// print a real number (interface version)

`define INTF_TO_REAL(name) \
    `ifdef FLOAT_REAL \
        (name) \
    `elsif HARD_FLOAT \
        (`REC_FN_TO_REAL(name)) \
    `else \
        (`FIXED_TO_FLOAT((name), `INTF_EXPONENT_REAL(name))) \
    `endif

`define INTF_PRINT_REAL(name) \
    $display(`"``name``=%0f`", `INTF_TO_REAL(name))

// force a real number (interface version)

`define INTF_FROM_REAL(expr, name) \
    `ifdef FLOAT_REAL \
        (expr) \
    `elsif HARD_FLOAT \
        (`REAL_TO_REC_FN(expr)) \
    `else \
		(`FLOAT_TO_FIXED((expr), `INTF_EXPONENT_REAL(name))) \
    `endif

`define INTF_FORCE_REAL(expr, name) \
    name = `INTF_FROM_REAL(expr, name)

// module definitions

module assertion_real #(
    `DECL_REAL(in),
    parameter name = "name"
) (
    `INPUT_REAL(in)
);

    localparam real min = -(`RANGE_PARAM_REAL(in));
    localparam real max = +(`RANGE_PARAM_REAL(in));

    always @(in) begin
        if (!((min <= `TO_REAL(in)) && (`TO_REAL(in) <= max))) begin
            $display("Real number %s with value %f out of range (%f to %f).", name, `TO_REAL(in), min, max);
            $fatal;
        end
    end

endmodule

module assign_real #(
    `DECL_REAL(in),
    `DECL_REAL(out)
) (
    `INPUT_REAL(in),
    `OUTPUT_REAL(out)
);
    `ifdef FLOAT_REAL
        assign out = in;
    `elsif HARD_FLOAT
        assign out = in;
    `else
        localparam integer lshift = `EXPONENT_PARAM_REAL(in) - `EXPONENT_PARAM_REAL(out);

        generate
            if (lshift >= 0) begin
                assign out = in <<< (+lshift);
            end else begin
                assign out = in >>> (-lshift);
            end
        endgenerate
    `endif
endmodule

module add_sub_real #(
    `DECL_REAL(a),
    `DECL_REAL(b),
    `DECL_REAL(c),
	parameter integer opcode=0
) (
    `INPUT_REAL(a),
    `INPUT_REAL(b),
    `OUTPUT_REAL(c)
);
`ifndef HARD_FLOAT
    `COPY_FORMAT_REAL(c, a_aligned);
    `COPY_FORMAT_REAL(c, b_aligned);

    `ASSIGN_REAL(a, a_aligned);
    `ASSIGN_REAL(b, b_aligned);

    generate
        if          (opcode == `ADD_OPCODE_REAL) begin
            assign c = a_aligned + b_aligned;
        end else if (opcode == `SUB_OPCODE_REAL) begin
            assign c = a_aligned - b_aligned;
        end else begin
            initial begin
                $display("ERROR: Invalid opcode.");
                $finish;
            end
        end
    endgenerate
`else
    logic subOp;
    assign subOp = (opcode == `SUB_OPCODE_REAL) ? 1'b1 : 1'b0;

    addRecFN #(
        .expWidth(`HARD_FLOAT_EXP_WIDTH),
        .sigWidth(`HARD_FLOAT_SIG_WIDTH)
    ) addRecFN_i (
        .control(`HARD_FLOAT_CONTROL),
        .subOp(subOp),
        .a(a),
        .b(b),
        .roundingMode(`HARD_FLOAT_ROUNDING),
        .out(c),
        .exceptionFlags()
    );
`endif
endmodule

module negate_real #(
    `DECL_REAL(in),
    `DECL_REAL(out)
) (
    `INPUT_REAL(in),
    `OUTPUT_REAL(out)
);
`ifndef HARD_FLOAT
    // align the input to the output format
    `COPY_FORMAT_REAL(out, in_aligned);
    `ASSIGN_REAL(in, in_aligned);

    // assign the output
    assign out = -in_aligned;
`else
    assign out = {~in[`HARD_FLOAT_SIGN_BIT], in[((`HARD_FLOAT_SIGN_BIT)-1):0]};
`endif
endmodule

module abs_real #(
    `DECL_REAL(in),
    `DECL_REAL(out)
) (
    `INPUT_REAL(in),
    `OUTPUT_REAL(out)
);
`ifndef HARD_FLOAT
    // align the input to the output format
    `COPY_FORMAT_REAL(out, in_aligned);
    `ASSIGN_REAL(in, in_aligned);

    // assign the output
    assign out = (in_aligned > 0) ? in_aligned : -in_aligned;
`else
    assign out = {1'b0, in[((`HARD_FLOAT_SIGN_BIT)-1):0]};
`endif
endmodule

module mul_real #(
    `DECL_REAL(a),
    `DECL_REAL(b),
    `DECL_REAL(c)
) (
    `INPUT_REAL(a),
    `INPUT_REAL(b),
    `OUTPUT_REAL(c)
);
`ifndef HARD_FLOAT
    // create wire to hold product result
    `MAKE_FORMAT_REAL(
        prod,
        `RANGE_PARAM_REAL(a) * `RANGE_PARAM_REAL(b),
        `WIDTH_PARAM_REAL(a) + `WIDTH_PARAM_REAL(b),
        `EXPONENT_PARAM_REAL(a) + `EXPONENT_PARAM_REAL(b)
    );

    // compute product
    assign prod = a * b;

    // assign result to output (which will left/right shift if necessary)
    `ASSIGN_REAL(prod, c);
`else
    mulRecFN #(
        .expWidth(`HARD_FLOAT_EXP_WIDTH),
        .sigWidth(`HARD_FLOAT_SIG_WIDTH)
    ) mulRecFN_i (
        .control(`HARD_FLOAT_CONTROL),
        .a(a),
        .b(b),
        .roundingMode(`HARD_FLOAT_ROUNDING),
        .out(c),
        .exceptionFlags()
    );
`endif
endmodule

module comp_real #(
    `DECL_REAL(a),
    `DECL_REAL(b),
    parameter integer opcode=0
) (
    `INPUT_REAL(a),
    `INPUT_REAL(b),
    output wire logic c
);
`ifndef HARD_FLOAT
    // compute the minimum of the two exponents and align both inputs to it

    localparam integer min_exponent = `MIN_MATH(`EXPONENT_PARAM_REAL(a), `EXPONENT_PARAM_REAL(b));

    `REAL_FROM_WIDTH_EXP(a_aligned, (`WIDTH_PARAM_REAL(a))+(`EXPONENT_PARAM_REAL(a))-min_exponent, min_exponent);
    `REAL_FROM_WIDTH_EXP(b_aligned, (`WIDTH_PARAM_REAL(b))+(`EXPONENT_PARAM_REAL(b))-min_exponent, min_exponent);

    `ASSIGN_REAL(a, a_aligned);
    `ASSIGN_REAL(b, b_aligned);

    generate
        if          (opcode == `GT_OPCODE_REAL) begin
            assign c = (a_aligned >  b_aligned) ? 1'b1 : 1'b0;
        end else if (opcode == `GE_OPCODE_REAL) begin
            assign c = (a_aligned >= b_aligned) ? 1'b1 : 1'b0;
        end else if (opcode == `LT_OPCODE_REAL) begin
            assign c = (a_aligned <  b_aligned) ? 1'b1 : 1'b0;
        end else if (opcode == `LE_OPCODE_REAL) begin
            assign c = (a_aligned <= b_aligned) ? 1'b1 : 1'b0;
        end else if (opcode == `EQ_OPCODE_REAL) begin
            assign c = (a_aligned == b_aligned) ? 1'b1 : 1'b0;
        end else if (opcode == `NE_OPCODE_REAL) begin
            assign c = (a_aligned != b_aligned) ? 1'b1 : 1'b0;
        end else begin
            initial begin
                $display("ERROR: Invalid opcode.");
                $finish;
            end
        end
    endgenerate
`else
    logic lt, eq, gt;

    compareRecFN #(
        .expWidth(`HARD_FLOAT_EXP_WIDTH),
        .sigWidth(`HARD_FLOAT_SIG_WIDTH)
    ) compareRecFN_i (
        .a(a),
        .b(b),
        .signaling(1'b0),
        .lt(lt),
        .eq(eq),
        .gt(gt),
        .unordered(),
        .exceptionFlags()
    );

    generate
        if          (opcode == `GT_OPCODE_REAL) begin
            assign c = gt;
        end else if (opcode == `GE_OPCODE_REAL) begin
            assign c = gt | eq;
        end else if (opcode == `LT_OPCODE_REAL) begin
            assign c = lt;
        end else if (opcode == `LE_OPCODE_REAL) begin
            assign c = lt | eq;
        end else if (opcode == `EQ_OPCODE_REAL) begin
            assign c = eq;
        end else if (opcode == `NE_OPCODE_REAL) begin
            assign c = ~eq;
        end else begin
            initial begin
                $display("ERROR: Invalid opcode.");
                $finish;
            end
        end
    endgenerate
`endif
endmodule

module ite_real #(
    `DECL_REAL(true),
    `DECL_REAL(false),
    `DECL_REAL(out)
) (
    input wire logic cond,
    `INPUT_REAL(true),
    `INPUT_REAL(false),
    `OUTPUT_REAL(out)
);
    `COPY_FORMAT_REAL(out,  true_aligned);
    `COPY_FORMAT_REAL(out, false_aligned);

    `ASSIGN_REAL(true,   true_aligned);
    `ASSIGN_REAL(false, false_aligned);

    assign out = (cond == 1'b1) ? true_aligned : false_aligned;
endmodule

module dff_real #(
    `DECL_REAL(d),
    `DECL_REAL(q),
	parameter real init=0
) (
    `INPUT_REAL(d),
    `OUTPUT_REAL(q),
    input wire logic rst,
    input wire logic clk,
    input wire logic cke
);
    // "var" for memory is kept internal
    // so that all ports are "wire" type nets
    `COPY_FORMAT_REAL(q, q_mem);
    `ASSIGN_REAL(q_mem, q);

    // align input to output
    `COPY_FORMAT_REAL(q, d_aligned);
    `ASSIGN_REAL(d, d_aligned);

    // align initial value to output format
    `COPY_FORMAT_REAL(q, init_aligned);
	`ASSIGN_CONST_REAL(init, init_aligned);

    // main DFF logic
    always @(posedge clk) begin
        if (rst == 1'b1) begin
            q_mem <= init_aligned;
        end else if (cke == 1'b1) begin
            q_mem <= d_aligned;
        end else begin
            q_mem <= q;
        end
    end
endmodule

module sync_rom_real #(
    `DECL_REAL(out),
    parameter integer addr_bits=1,
    parameter integer data_bits=1,
    parameter integer data_expt=1,
    parameter file_path=""
) (
    input wire logic [(addr_bits-1):0] addr,
    `OUTPUT_REAL(out),
    input wire logic clk,
    input wire logic ce
);
    // load the ROM
    logic signed [(data_bits-1):0] rom [0:((2**addr_bits)-1)];
    initial begin
        $readmemb(file_path, rom);
    end

    // read from the ROM
    logic signed [(data_bits-1):0] data;
    always @(posedge clk) begin
        if (ce) begin
            data <= rom[addr];
        end
    end

    // Assign to output.  We have to explicitly handle FLOAT_REAL case
    // because ROM data is always stored with fixed-point formatting,
    // even when FLOAT_REAL is defined.
    `ifdef FLOAT_REAL
        assign out = `FIXED_TO_FLOAT(data, data_expt);
    `elsif HARD_FLOAT
        assign out = data;
    `else
        localparam `RANGE_PARAM_REAL(data) = 2.0**(data_bits+data_expt-1);
        localparam `WIDTH_PARAM_REAL(data) = data_bits;
        localparam `EXPONENT_PARAM_REAL(data) = data_expt;
        `ASSIGN_REAL(data, out);
    `endif
endmodule

// adapted from the example on page 119-120 here:
// https://www.xilinx.com/support/documentation/sw_manuals/xilinx2019_2/ug901-vivado-synthesis.pdf

module sync_ram_real #(
    `DECL_REAL(out),
    parameter integer addr_bits=1,
    parameter integer data_bits=1,
    parameter integer data_expt=1
) (
    input wire logic [(addr_bits-1):0] addr,
    input wire logic signed [(data_bits-1):0] din,
    `OUTPUT_REAL(out),
    input wire logic clk,
    input wire logic ce,
    input wire logic we
);
    // memory contents
    logic signed [(data_bits-1):0] ram [0:((2**addr_bits)-1)];

    // RAM I/O
    logic signed [(data_bits-1):0] data;
    always @(posedge clk) begin
        if (ce) begin
            if (we) begin
                ram[addr] <= din;
            end
            data <= ram[addr];
        end
    end

    // Assign to output.  We have to explicitly handle FLOAT_REAL case
    // because ROM data is always stored with fixed-point formatting,
    // even when FLOAT_REAL is defined.
    `ifdef FLOAT_REAL
        assign out = `FIXED_TO_FLOAT(data, data_expt);
    `elsif HARD_FLOAT
        assign out = data;
    `else
        localparam `RANGE_PARAM_REAL(data) = 2.0**(data_bits+data_expt-1);
        localparam `WIDTH_PARAM_REAL(data) = data_bits;
        localparam `EXPONENT_PARAM_REAL(data) = data_expt;
        `ASSIGN_REAL(data, out);
    `endif
endmodule

// measuring the width of an integer

module meas_uint_width #(
    parameter integer in_width=1,
    parameter integer out_width=1
) (
    input wire [(in_width-1):0] in,
    output reg [(out_width-1):0] out
);
    integer i;
    always @(*) begin
        if (in == 0) begin
            out = 0;
        end else begin
            for (i=0; i<in_width; i=i+1) begin
                if (in[i]) begin
                    out = i + 1;
                end
            end
        end
    end
endmodule

// Compressing an integer
// TODO: handle "inf" case for HARD_FLOAT

module compress_uint #(
    parameter integer in_width=1,
    `DECL_REAL(out)
) (
    input wire [(in_width-1):0] in,
    `OUTPUT_REAL(out)
);
    `ifdef FLOAT_REAL
        real x, y;
        always @(in) begin
            if (in == 0.0) begin
                x = 0.0;
                y = 0.0;
            end else begin
                x = $floor($ln(1.0*in)/$ln(2.0)) + 1.0;
                y = ((1.0*in)/(2.0**(x-1.0))) - 1.0;
            end
        end
        assign out = x + y;
    `elsif HARD_FLOAT
        // make signed version of the input, as needed by INT_TO_REAL
        logic signed [in_width:0] in_signed;
        assign in_signed = {1'b0, in};
        `INT_TO_REAL(in_signed, (in_width+1), in_as_float);

        // extract exponent and significand (the sign is unused)
        logic sign;
        logic [(`HARD_FLOAT_EXP_WIDTH):0] exp;
        logic [((`HARD_FLOAT_SIG_WIDTH)-2):0] fract;
        assign {sign, exp, fract} = in_as_float;

        // represent the width as a floating-point number
        localparam integer count_width = $clog2(in_width+1);
        localparam [(`HARD_FLOAT_EXP_WIDTH):0] exp_bias =
            ((2**((`HARD_FLOAT_EXP_WIDTH)-1))+1) +  // recoding bias
            ((2**((`HARD_FLOAT_EXP_WIDTH)-1))-1);   // exponent bias
        logic signed [count_width:0] count_signed;
        assign count_signed = exp - exp_bias;
        `INT_TO_REAL(count_signed, (count_width+1), exp_as_float);

        // convert the fractional part to a floating-point number

        assign fract_as_float = {1'b0, exp_bias, fract};

        // add the two together
        `ADD_REAL(exp_as_float, fract_as_float, result);

        // special handling for zero
        assign out = (in==0) ? 0 : result;
    `else
        // numbers of bits needed to represent the input width
        localparam integer count_width = $clog2(in_width+1);

        // number of bits needed to represent the output
        localparam integer data_width = 1 + count_width + (in_width-1);

        // measure the input width, which can be 0 through in_width, inclusive
        `MEAS_UINT_WIDTH(in, in_width, count, count_width);

        // re-align input
        logic [(in_width-2):0] aligned;
        assign aligned = in << (in_width-count);

        // format signals into a fixed-point signal
        `MAKE_FORMAT_REAL(data, (in_width+1), data_width, (-in_width+1));
        assign data = {1'b0, count, aligned};

        // assign output
        `ASSIGN_REAL(data, out);
    `endif
endmodule

`endif // `ifndef __SVREAL_SV__


`timescale 1ns / 1ps

`include "svreal.sv"

module test_arith(
    // generic inputs
    input real a_i,
    input real b_i,
    // operators
    output real min_o,
    output real max_o,
    output real add_o,
    output real sub_o,

    output real neg_o,
    output real abs_o
);
    // create a_i input
    `REAL_FROM_WIDTH_EXP(a_int, 16, -8);
    assign `FORCE_REAL(a_i, a_int);

    // create b_i input
    `REAL_FROM_WIDTH_EXP(b_int, 17, -9);
    assign `FORCE_REAL(b_i, b_int);

    // min
    `MIN_REAL_GENERIC(a_int, b_int, min_int, 20);
    assign min_o = `TO_REAL(min_int);

    // max
    `MAX_REAL_GENERIC(a_int, b_int, max_int, 21);
    assign max_o = `TO_REAL(max_int);

    // add
    `ADD_REAL_GENERIC(a_int, b_int, add_int, 22);
    assign add_o = `TO_REAL(add_int);

    // sub
    `SUB_REAL_GENERIC(a_int, b_int, sub_int, 23);
    assign sub_o = `TO_REAL(sub_int);

    // mul
    `MUL_REAL_GENERIC(a_int, b_int, mul_int, 24);
    assign mul_o = `TO_REAL(mul_int);

    // negate
    `NEGATE_REAL(a_int, neg_int);
    assign neg_o = `TO_REAL(neg_int);

    // absolute value
    `ABS_REAL(a_int, abs_int);
    assign abs_o = `TO_REAL(abs_int);
endmodule


`timescale 1ns/1ps

`include "svreal.sv"

module test_clog2(
    input real in_,

    output signed [31:0] out
);
    assign out = clog2_math(in_);
endmodule


`timescale 1ns / 1ps

`include "svreal.sv"

module test_comp(
    // generic inputs
    input real a_i,
    input real b_i,
    // comparisons
    output lt_o,
    output le_o,
    output gt_o,
    output ge_o,
    output eq_o,
    output ne_o
);
    // create a_i input
    `REAL_FROM_WIDTH_EXP(a_int, 16, -8);
    assign `FORCE_REAL(a_i, a_int);

    // create b_i input
    `REAL_FROM_WIDTH_EXP(b_int, 17, -9);
    assign `FORCE_REAL(b_i, b_int);

    // comparisons
    `LT_INTO_REAL(a_int, b_int, lt_o);
    `LE_INTO_REAL(a_int, b_int, le_o);
    `GT_INTO_REAL(a_int, b_int, gt_o);
    `GE_INTO_REAL(a_int, b_int, ge_o);

    `EQ_INTO_REAL(a_int, b_int, eq_o);
    `NE_INTO_REAL(a_int, b_int, ne_o);
endmodule


`timescale 1ns/1ps

`include "svreal.sv"

`ifndef WIDTH
    `define WIDTH 8
`endif

module test_compress_uint (
    input [((`WIDTH)-1):0] in_,
    output real out

);
    `COMPRESS_UINT(in_, (`WIDTH), val);
    assign out = `TO_REAL(val);
endmodule


`timescale 1ns / 1ps

`include "svreal.sv"

module test_const #(
    parameter real a_const=0.0,
    parameter real b_const=0.0,
    parameter real c_const=0.0
) (
    output real a_o,
    input real b_i,
    output real b_o,
    input real c_i,
    output real c_o

    assign b_o = `TO_REAL(b_o_int);

    // produce c_o output
    `MAKE_REAL(c_i_int, 10);
    assign `FORCE_REAL(c_i, c_i_int);
    `ADD_CONST_REAL(c_const, c_i_int, c_o_int);
    assign c_o = `TO_REAL(c_o_int);
endmodule

);
    // produce a_o output
    `MAKE_CONST_REAL(a_const, a_const_int);
    assign a_o = `TO_REAL(a_const_int);


`timescale 1ns / 1ps

`include "svreal.sv"

module test_conv(
    // real to integer
    input real r2i_i,
    output signed [7:0] r2i_o,
    // integer to real
    input signed [7:0] i2r_i,
    output real i2r_o
);
    // create r2i_i input
    `REAL_FROM_WIDTH_EXP(r2i_int, 16, -8);

    `REAL_INTO_INT(r2i_int, 8, r2i_o);

    // integer to real
    `INT_TO_REAL(i2r_i, 8, i2r_int);
    assign i2r_o = `TO_REAL(i2r_int);
endmodule


`timescale 1ns / 1ps

`include "svreal.sv"

    input rst_i,
    input clk_i,
    input cke_i
);
    // create data input
    `REAL_FROM_WIDTH_EXP(d_int, 16, -8);
    assign `FORCE_REAL(d_i, d_int);

    // create data output
    `REAL_FROM_WIDTH_EXP(q_int, 17, -9);
    assign q_o = `TO_REAL(q_int);

    // instantiate DFF
    `DFF_INTO_REAL(d_int, q_int, rst_i, clk_i, cke_i, init);
endmodule

module test_dff #(
    parameter real init=0.0
) (


`timescale 1ns/1ps

`include "svreal.sv"

module test_float;
    real inf, nan;  // will be assigned during test

    `MAKE_REAL(x, 10); // range is arbitrary for floating point...

    real out;
    assign out = `TO_REAL(x);

    task check_equals(input real a, input real b);
        if (a == b) begin
            $display("%e == %e", a, b);
        end else begin
            $error("%e != %e", a, b);
        end
    endtask

    task check_tol(input real meas, input real expct, input real tol);
        if (((expct-tol)<meas) && (meas<(expct+tol))) begin
            $display("%e is close to %e", meas, expct);
        end else begin
            $error("%e is not close to %e", meas, expct);
        end
    endtask

    function isnan(input real a);
        logic [63:0] data;
        data = $realtobits(a);

            return 1;
        end else begin
            return 0;
        end
    endfunction

    initial begin
        // inf and nan need to be assigned like this because
        // Xcelium will error out if we just did inf = 1e1000
        inf = $bitstoreal(64'h7FF0000000000000);
        nan = $bitstoreal(64'h7FF8000000000000);

        `FORCE_REAL(0.0, x);
        #1;
        check_equals(out, 0.0);
        #1;

        `FORCE_REAL(1.23, x);
        #1;
        check_tol(out, 1.23, 1e-5);
        #1;

        `FORCE_REAL(-4.56, x);
        #1;
        check_tol(out, -4.56, 1e-5);
        #1;

        `FORCE_REAL(1e15, x);
        #1;
        check_tol(out, 1e15, 1e9);
        #1;

        `FORCE_REAL(1e-15, x);
        #1;
        check_tol(out, 1e-15, 1e-20);
        #1;

        `FORCE_REAL(1e-40, x);  // double can represent this but not the recoded format
        #1;
        check_equals(out, 0.0);
        #1;

        `FORCE_REAL(1e-100, x);  // double can represent this but not the recoded format
        #1;
        check_equals(out, 0.0);
        #1;

        `FORCE_REAL(1e-315, x);  // subnormal for double; too small for recoded format
        #1;
        check_equals(out, 0.0);
        #1;

        `FORCE_REAL(1e-400, x);  // too small for double and recoded format
        #1;
        check_equals(out, 0.0);
        #1;

        `FORCE_REAL(inf, x);
        #1;
        check_equals(out, inf);
        #1;

        `FORCE_REAL(-inf, x);
        #1;
        check_equals(out, -inf);
        #1;

        `FORCE_REAL(nan, x);
        #1;
        if (isnan(out)) begin
            $display("out is nan");
        end else begin
            $error("out is not nan");
        end
        #1;

        `FORCE_REAL(1e100, x);
        #1;
        check_equals(out, inf);
        #1;

        `FORCE_REAL(-1e100, x);
        #1;
        check_equals(out, -inf);
        #1;

        $finish;
    end
endmodule


`timescale 1ns / 1ps

`include "svreal.sv"

module level3 #(
    `DECL_REAL(a),
    `DECL_REAL(b),
    `DECL_REAL(c)
) (
    `INPUT_REAL(a),
    `INPUT_REAL(b),
    `OUTPUT_REAL(c)
);
    `MUL_INTO_REAL(a, b, c);
endmodule

module level2 #(
    `DECL_REAL(a),
    `DECL_REAL(b),
    `DECL_REAL(c)
) (
    `INPUT_REAL(a),
    `INPUT_REAL(b),
    `OUTPUT_REAL(c)
);
    level3 #(
        `PASS_REAL(a, a),

        `PASS_REAL(c, c)
    ) inner (
        .a(a),
        .b(b),
        .c(c)
    );
endmodule

module level1 #(
    `DECL_REAL(a),
    `DECL_REAL(b),
    `DECL_REAL(c)
) (
    `INPUT_REAL(a),
    `INPUT_REAL(b),
    `OUTPUT_REAL(c)
);
    level2 #(
        `PASS_REAL(a, a),
        `PASS_REAL(b, b),
        `PASS_REAL(c, c)
    ) inner (
        .a(a),
        .b(b),
        .c(c)
    );
endmodule

module test_hier(
    input real a_i,
    input real b_i,
    output real c_o
);
    // create a_int signal
    `REAL_FROM_WIDTH_EXP(a_int, 16, -8);
    assign `FORCE_REAL(a_i, a_int);

    // create b_int signal
    `REAL_FROM_WIDTH_EXP(b_int, 17, -9);
    assign `FORCE_REAL(b_i, b_int);

    // create c_int signal
    `REAL_FROM_WIDTH_EXP(c_int, 18, -10);
    assign c_o = `TO_REAL(c_int);

    level1 #(
        `PASS_REAL(a, a_int),
        `PASS_REAL(b, b_int),
        `PASS_REAL(c, c_int)
    ) inner (
        .a(a_int),
        .b(b_int),
        .c(c_int)
    );
endmodule


`timescale 1ns/1ps

`include "svreal.sv"

module test_iface(
    input real a_i,
    input real b_i,
    output real c_o
);
    // create a_int interface
    svreal #(`REAL_INTF_PARAMS(value, 16, -8)) a_int ();
    assign `INTF_FORCE_REAL(a_i, a_int.value);

    // create b_int interface
    svreal #(`REAL_INTF_PARAMS(value, 17, -9)) b_int ();
    assign `INTF_FORCE_REAL(b_i, b_int.value);

    level1 inner (
        .a(a_int),
        .b(b_int),
        .c(c_int)
    );
endmodule

    // create c_int interface
    svreal #(`REAL_INTF_PARAMS(value, 18, -10)) c_int ();


`timescale 1ns/1ps

`include "svreal.sv"

interface svreal #(
    `INTF_DECL_REAL(value)
);
    `INTF_MAKE_REAL(value);
    modport in(`MODPORT_IN_REAL(value));
    modport out(`MODPORT_OUT_REAL(value));
endinterface

module level4 #(
    `DECL_REAL(a),
    `DECL_REAL(b),
    `DECL_REAL(c)
) (
    `INPUT_REAL(a),
    `INPUT_REAL(b),
    `OUTPUT_REAL(c)
);
    `MUL_INTO_REAL(a, b, c);
endmodule

module level3 (svreal.in a, svreal.in b, svreal.out c);
    generate
        `ifndef INTF_USE_LOCAL
            level4 #(
                `INTF_PASS_REAL(a, a.value),
                `INTF_PASS_REAL(b, b.value),
                `INTF_PASS_REAL(c, c.value)
            ) inner (
                .a(a.value),
                .b(b.value),
                .c(c.value)
            );
        `else
            `INTF_INPUT_TO_REAL(a.value, a_value);
            `INTF_INPUT_TO_REAL(b.value, b_value);
            `INTF_OUTPUT_TO_REAL(c.value, c_value);
            level4 #(
                `PASS_REAL(a, a_value),
                `PASS_REAL(b, b_value),
                `PASS_REAL(c, c_value)
            ) inner (
                .a(a_value),
                .b(b_value),
                .c(c_value)
            );
        `endif
    endgenerate
endmodule

module level2 (svreal.in a, svreal.in b, svreal.out c);
    level3 inner(.a(a), .b(b), .c(c));
endmodule

module level1 (svreal.in a, svreal.in b, svreal.out c);
    level2 inner(.a(a), .b(b), .c(c));
endmodule


`timescale 1ns/1ps

`include "svreal.sv"

module test_iface_synth(
    input logic signed [15:0] a_value,
    input logic signed [16:0] b_value,
    output logic signed [17:0] c_value
);
    svreal #(`REAL_INTF_PARAMS(value, $size(a_value), -8)) a ();
    svreal #(`REAL_INTF_PARAMS(value, $size(b_value), -9)) b ();
    svreal #(`REAL_INTF_PARAMS(value, $size(c_value), -10)) c ();

        .b(b),
        .c(c)
    );
endmodule

    assign a.value = a_value;
    assign b.value = b_value;
    assign c_value = c.value;


`timescale 1ns / 1ps

`include "svreal.sv"

module test_ite(
    // generic inputs
    input real a_i,

    // create a_i input
    `REAL_FROM_WIDTH_EXP(a_int, 16, -8);
    assign `FORCE_REAL(a_i, a_int);

    // create b_i input
    `REAL_FROM_WIDTH_EXP(b_int, 17, -9);
    assign `FORCE_REAL(b_i, b_int);

    // mux
    `ITE_REAL_GENERIC(cond_i, a_int, b_int, ite_int, 25);
    assign ite_o = `TO_REAL(ite_int);
endmodule

    input real b_i,
    // if-then-else
    input cond_i,


`timescale 1ns/1ps

`include "svreal.sv"

module test_meas_width (

    input [7:0] in_,
    output [7:0] out
);
    `MEAS_UINT_WIDTH_INTO(in_, 8, out, 8);


`timescale 1ns / 1ps

`include "svreal.sv"

module test_sync_ram (
    input [1:0] addr,
    input [((`WIDTH)-1):0] din,
    output real out,

    input ce,
    input we
);
    // instantiate the RAM
    `SYNC_RAM_REAL(addr, din, out_int, clk, ce, we, 2, 18, -12);

    // wire up the RAM output
    assign out = `TO_REAL(out_int);
endmodule


    assign out = `TO_REAL(out_int);
endmodule

module test_sync_rom (
    input [1:0] addr,
    output real out,
    input clk,


`timescale 1ns / 1ps

`include "svreal.sv"

`define A_WIDTH 16
`define B_WIDTH 17
`define I2R_WIDTH 8
`define R2I_WIDTH 8

module test_synth (
    // real-number inputs

    output wire logic signed [21:0] add_ext,
    output wire logic signed [22:0] sub_ext,
    output wire logic signed [23:0] mul_ext,
    output wire logic signed [24:0] mux_ext,
    // conversion I/O
    input wire logic signed [`I2R_WIDTH-1:0] i2r_i_ext,
    output wire logic signed [`I2R_WIDTH-1:0] i2r_o_ext,
    output wire logic signed [`R2I_WIDTH-1:0] r2i_o_ext,
    // comparison I/O
    output wire logic lt_ext,
    output wire logic le_ext,
    output wire logic gt_ext,
    output wire logic ge_ext,
    // compression I/O
    input wire logic [31:0] compress_i_ext,
    output wire logic [((`LONG_WIDTH_REAL)-1):0] compress_o_ext,
    // control I/O
    input wire logic sel_ext,
    input wire logic rst_ext,
    input wire logic clk_ext,
    input wire logic ce_ext
);
    // create signals
    `REAL_FROM_WIDTH_EXP(a, $size(a_ext), -8);
    `REAL_FROM_WIDTH_EXP(b, $size(b_ext), -9);
    assign a = a_ext;
    assign b = b_ext;

    // min
    `MIN_REAL_GENERIC(a, b, min_o, $size(min_ext));
    assign min_ext = min_o;

    // max
    `MAX_REAL_GENERIC(a, b, max_o, $size(max_ext));
    assign max_ext = max_o;

    // add
    `ADD_REAL_GENERIC(a, b, add_o, $size(add_ext));
    assign add_ext = add_o;

    // sub
    `SUB_REAL_GENERIC(a, b, sub_o, $size(sub_ext));
    assign sub_ext = sub_o;

    // mul
    `MUL_REAL_GENERIC(a, b, mul_o, $size(mul_ext));
    assign mul_ext = mul_o;

    // mux
    `ITE_REAL_GENERIC(sel_ext, a, b, mux_o, $size(mux_ext));
    assign mux_ext = mux_o;

    // negation
    `NEGATE_REAL(a, neg_o);
    assign neg_ext = neg_o;

    // absolute value
    `ABS_REAL(a, abs_o);
    assign abs_ext = abs_o;

    // integer to real
    `INT_TO_REAL(i2r_i_ext, $size(i2r_i_ext), i2r_o_int);
    assign i2r_o_ext = i2r_o_int;

    // real to integer
    `REAL_TO_INT(a, $size(r2i_o_ext), r2i_o_int);
    assign r2i_o_ext = r2i_o_int;

    // dff
    `DFF_REAL(a, dff_o, rst_ext, clk_ext, ce_ext, 1.23);
    assign dff_ext = dff_o;

    // comparisons
    `LT_INTO_REAL(a, b, lt_ext);
    `LE_INTO_REAL(a, b, le_ext);
    `GT_INTO_REAL(a, b, gt_ext);
    `GE_INTO_REAL(a, b, ge_ext);

    // uint compression
    `COMPRESS_UINT(compress_i_ext, $size(compress_i_ext), compress_o);
    assign compress_o_ext = compress_o;
endmodule

    input wire logic signed [`A_WIDTH-1:0] a_ext,
    input wire logic signed [`B_WIDTH-1:0] b_ext,
    // unary op I/O
    output wire logic signed [`A_WIDTH-1:0] neg_ext,
    output wire logic signed [`A_WIDTH-1:0] abs_ext,


//
// SPDX-License-Identifier: CERN-OHL-S-2.0+
//
// Copyright (C) 2020-21 Embecosm Limited <www.embecosm.com>
// Contributed by:
// Matthew Johns <mrj1g17@soton.ac.uk>
// Byron Theobald <bt4g16@soton.ac.uk>
//
// This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,
// INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR
// A PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 for applicable
// conditions.
// Source location: https://github.com/AI-Vector-Accelerator
//

package accelerator_pkg;

// Arithmetic operations inside PEs
typedef enum logic [3:0] {
    PE_ARITH_ADD,
    PE_ARITH_SUB,
    PE_ARITH_LSHIFT, // left-shift
    PE_ARITH_MUL,
    PE_ARITH_MULADD, // multiply-add
    PE_ARITH_XOR,
    PE_ARITH_RSHIFT_LOG, // logical right-shift
    PE_ARITH_RSHIFT_AR, // arithmetic right-shift
    PE_ARITH_OR,
    PE_ARITH_AND
} pe_arith_op_t;

// PE output mode
// PE_OP_MODE_RESULT: pass through arithmetic result
// PE_OP_MODE_PASS_MAX: pass larger of operands A and B
// PE_OP_MODE_PASS_MIN: pass smaller of operands A and B

    PE_OP_MODE_RESULT,
    PE_OP_MODE_PASS_MAX,
    PE_OP_MODE_PASS_MIN
} pe_output_mode_t;

// PE saturation mode
// PE_SAT_NONE: not saturation of output
// PE_SAT: saturate to element width, keep decimal in same position
// PE_SAT_UPPER: saturate to element width, keeping upper half of result only
typedef enum logic [1:0] {
    PE_SAT_NONE,
    PE_SAT,
    PE_SAT_UPPER
} pe_saturate_mode_t;

// PE operand selection - select B operand for PE
// PE_OPERAND_VS1: vector from vs1 vector register
// PE_OPERAND_SCALAR: scalar passed from x-register
// PE_OPERAND_IMMEDIATE: scalar immediate passed from instruction
// PE_OPERAND_RIPPLE: configure PEs in ripple mode for reductions
typedef enum logic [1:0] {
    PE_OPERAND_VS1,
    PE_OPERAND_SCALAR,
    PE_OPERAND_IMMEDIATE,
    PE_OPERAND_RIPPLE
} pe_operand_t;

// Source of data written back to vd in vector registers
// VREG_WB_SRC_ARITH: data output from arithmetic stage
// VREG_WB_SRC_MEMORY: data retrieved from RAM
// VREG_WB_SRC_SCALAR: scalar value taken from scalar replicator
typedef enum logic [1:0] {
    VREG_WB_SRC_ARITH,
    VREG_WB_SRC_MEMORY,
    VREG_WB_SRC_SCALAR
} vreg_wb_src_t;

// Source of address of selected vector register
// VS3_ADDR_SRC_DECODE: address from decoder
// VS3_ADDR_SRC_VLSU: address from vlsu / address unit
typedef enum logic {
    VS3_ADDR_SRC_DECODE,
    VS3_ADDR_SRC_VLSU
} vreg_addr_src_t;

// Source of data returned to CPU by via apu_result
// APU_RESULT_SRC_VL: return VL value. For vsetvli
// APU_RESULT_SRC_VS2_0: return first element of vs2. For vmv.x.s
typedef enum logic {
    APU_RESULT_SRC_VL,
    APU_RESULT_SRC_VS2_0
}   apu_result_src_t;

// Major opcodes converted into 2 bits for the vector accelerator
// parameter V_MAJOR_LOAD_FP   = 2'b00;
// parameter V_MAJOR_STORE_FP  = 2'b01;
// parameter V_MAJOR_OP_V      = 2'b10;
// parameter V_MAJOR_CUSTOM    = 2'b11;
parameter V_MAJOR_LOAD_FP   = 7'b0000111;
parameter V_MAJOR_STORE_FP  = 7'b0100111;
parameter V_MAJOR_OP_V      = 7'b1010111;
// parameter V_MAJOR_CUSTOM    = 7'b;

// funct3 bit fields from vector instructions (describe operand/source types)
parameter V_OPIVV = 3'b000;
parameter V_OPFVV = 3'b001;
parameter V_OPMVV = 3'b010;
parameter V_OPIVI = 3'b011;
parameter V_OPIVX = 3'b100;
parameter V_OPFVF = 3'b101;
parameter V_OPMVX = 3'b110;
parameter V_OPCFG = 3'b111;

endpackage