/*
 * Copyright 2025 BSC*
 * *Barcelona Supercomputing Center (BSC)
 *
 * SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
 *
 * Licensed under the Solderpad Hardware License v 2.1 (the “License”); you
 * may not use this file except in compliance with the License, or, at your
 * option, the Apache License version 2.0. You may obtain a copy of the
 * License at
 *
 * https://solderpad.org/licenses/SHL-2.1/
 *
 * Unless required by applicable law or agreed to in writing, any work
 * distributed under the License is distributed on an “AS IS” BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

module mul_unit
    import drac_pkg::*;
    import riscv_pkg::*;
(
    input  logic                   clk_i,          // Clock Signal
    input  logic                   rstn_i,         // Negative reset signal
    input  logic                   flush_mul_i,     // Kill on fly instructions signal
    input  rr_exe_arith_instr_t    instruction_i,  // New instruction
    output exe_wb_scalar_instr_t   instruction_o   // Output instruction
);

// Declarations
bus64_t data_src1, data_src2;
logic same_sign;
logic int_32_0_d, int_32_0_q, int_32_1_q;
logic neg_def_0_d, neg_def_0_q, neg_def_1_q;
logic [2:0] type_0_d;
logic [2:0] type_0_q;
logic [2:0] type_1_q;
bus64_t src1_def_q, src1_def_d;
bus64_t src2_def_q, src2_def_d;
logic [95:0] result_low_d;
logic [95:0] result_high_d;
logic [95:0] result_low_q;
logic [95:0] result_high_q;
bus128_t result_128;
bus128_t result_128_def;
bus64_t result_32_aux;
bus64_t result_32;
bus64_t result_64;
exe_wb_scalar_instr_t instruction_0_d;
exe_wb_scalar_instr_t instruction_0_q;
exe_wb_scalar_instr_t instruction_1_q;
exe_wb_scalar_instr_t instruction_s1;
exe_wb_scalar_instr_t instruction_s2;

assign data_src1 = instruction_i.data_rs1;
assign data_src2 = instruction_i.data_rs2;

assign same_sign = instruction_i.instr.op_32 ? ~(data_src2[31] ^ data_src1[31]) : ~(data_src2[63] ^ data_src1[63]);

assign type_0_d = instruction_i.instr.mem_size[2:0];

assign int_32_0_d = instruction_i.instr.op_32 & instruction_0_d.valid;

function [63:0] trunc_65_64(input [64:0] val_in);
trunc_65_64 = val_in[63:0];
endfunction

function [127:0] trunc_129_128(input [128:0] val_in);
trunc_129_128 = val_in[127:0];
endfunction

// Source Operands, convert if source is negative and operation is signed
always_comb begin
    case (type_0_d)
        3'b000: begin  // Multiply word, Low part, Signed - MUL , MULW
            src1_def_d   = ((data_src1[63] & !int_32_0_d) | (data_src1[31]  & int_32_0_d)) ?
                             trunc_65_64(~data_src1 + 64'b1) : data_src1;
            src2_def_d   = ((data_src2[63] & !int_32_0_d) | (data_src2[31]  & int_32_0_d)) ?
                             trunc_65_64(~data_src2 + 64'b1) : data_src2;
            neg_def_0_d  = !same_sign;
        end
        3'b001: begin  // Multiply word, High part, Signed - MULH
            src1_def_d   = (data_src1[63]) ? trunc_65_64(~data_src1 + 64'b1) : data_src1;
            src2_def_d   = (data_src2[63]) ? trunc_65_64(~data_src2 + 64'b1) : data_src2;
            neg_def_0_d  = !same_sign;
        end
        3'b010: begin  // Multiply word, High part, SignedxUnsigned - MULHSU
            src1_def_d   = (data_src1[63]) ? trunc_65_64(~data_src1 + 64'b1) : data_src1;
            src2_def_d   = data_src2;
            neg_def_0_d  = data_src1[63];
        end
        3'b011: begin  //  Multiply word, High part, Unsigned Unsigned MULHU
            src1_def_d   = data_src1;
            src2_def_d   = data_src2;
            neg_def_0_d  = 1'b0;
        end
        default: begin
            src1_def_d   = 64'b0;
            src2_def_d   = 64'b0;
            neg_def_0_d  = 1'b0;
        end
    endcase
end

assign result_low_d  = src1_def_q * src2_def_q[31:0];
assign result_high_d = src1_def_q * src2_def_q[63:32];

// 32-bit multiplication MULW, operation finished
assign result_32_aux = neg_def_0_q ? trunc_65_64(~result_low_d[63:0] + 64'b1) : result_low_d[63:0];
assign result_32 = {{32{result_32_aux[31]}},result_32_aux[31:0]};

assign instruction_0_d.valid         = instruction_i.instr.valid & (instruction_i.instr.unit == UNIT_MUL);
assign instruction_0_d.pc            = instruction_i.instr.pc;
assign instruction_0_d.bpred         = instruction_i.instr.bpred;
assign instruction_0_d.rs1           = instruction_i.instr.rs1;
assign instruction_0_d.rd            = instruction_i.instr.rd;
assign instruction_0_d.regfile_we    = instruction_i.instr.regfile_we;
assign instruction_0_d.instr_type    = instruction_i.instr.instr_type;
assign instruction_0_d.stall_csr_fence = instruction_i.instr.stall_csr_fence;
assign instruction_0_d.csr_addr      = instruction_i.instr.imm[CSR_ADDR_SIZE-1:0];
assign instruction_0_d.prd           = instruction_i.prd;
assign instruction_0_d.checkpoint_done = instruction_i.checkpoint_done;
assign instruction_0_d.chkp          = instruction_i.chkp;
assign instruction_0_d.gl_index      = instruction_i.gl_index;
assign instruction_0_d.branch_taken  = 1'b0;
assign instruction_0_d.result_pc     = 0;
assign instruction_0_d.result        = instruction_i.instr.imm;
assign instruction_0_d.mem_type      = instruction_i.instr.mem_type;
assign instruction_0_d.ex            = '0;
assign instruction_0_d.fp_status     = '0;
assign instruction_0_d.vl            = instruction_i.instr.vl;
assign instruction_0_d.sew            = instruction_i.instr.sew;
assign instruction_0_d.lmul          = instruction_i.instr.lmul;

`ifdef SIM_KONATA_DUMP
assign instruction_0_d.id            = instruction_i.instr.id;
`endif

//--------------------------------------------------------------------------------------------------
//----- FIRST STAGE  ------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------

always_ff@(posedge clk_i, negedge rstn_i) begin
    if(~rstn_i) begin
        instruction_0_q          <= 'h0;
        type_0_q                 <= 3'b111;
        neg_def_0_q              <= 1'b0;
        int_32_0_q               <= 1'b0;
        src1_def_q               <= 'h0;
        src2_def_q               <= 'h0;
    end else if (flush_mul_i | (~instruction_0_d.valid)) begin
        instruction_0_q          <= 'h0;
        type_0_q                 <= 3'b111;
        neg_def_0_q              <= 1'b0;
        int_32_0_q               <= 1'b0;
        src1_def_q               <= 'h0;
        src2_def_q               <= 'h0;
    end else begin
        instruction_0_q          <= instruction_0_d;
        type_0_q                 <= type_0_d;
        int_32_0_q               <= int_32_0_d;
        neg_def_0_q              <= neg_def_0_d;
        src1_def_q               <= src1_def_d;
        src2_def_q               <= src2_def_d;
    end
end

assign instruction_s1.valid         = instruction_0_q.valid & ~(int_32_1_q);
assign instruction_s1.pc            = instruction_0_q.pc;
assign instruction_s1.bpred         = instruction_0_q.bpred;
assign instruction_s1.rs1           = instruction_0_q.rs1;
assign instruction_s1.rd            = instruction_0_q.rd;
assign instruction_s1.regfile_we    = instruction_0_q.regfile_we;
assign instruction_s1.instr_type    = instruction_0_q.instr_type;
assign instruction_s1.stall_csr_fence = instruction_0_q.stall_csr_fence;
assign instruction_s1.csr_addr      = instruction_0_q.result[CSR_ADDR_SIZE-1:0];
assign instruction_s1.prd           = instruction_0_q.prd;
assign instruction_s1.checkpoint_done = instruction_0_q.checkpoint_done;
assign instruction_s1.chkp          = instruction_0_q.chkp;
assign instruction_s1.gl_index      = instruction_0_q.gl_index;
assign instruction_s1.branch_taken  = 1'b0;
assign instruction_s1.result_pc     = 0;
assign instruction_s1.result        = result_32;
assign instruction_s1.ex            = instruction_0_q.ex;
assign instruction_s1.vl            = instruction_0_q.vl;
assign instruction_s1.sew            = instruction_0_q.sew;
assign instruction_s1.lmul          = instruction_0_q.lmul;

`ifdef SIM_KONATA_DUMP
assign instruction_s1.id            = instruction_0_q.id;
`endif
assign instruction_s1.fp_status     = 'h0;
assign instruction_s1.mem_type      = instruction_0_q.mem_type;

//--------------------------------------------------------------------------------------------------
//----- SECOND STAGE  ------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------

always_ff@(posedge clk_i, negedge rstn_i) begin
    if(~rstn_i) begin
        instruction_1_q          <= 'h0;
        type_1_q                 <= 3'b111;
        int_32_1_q               <= 1'b0;
        neg_def_1_q              <= 1'b0;
        result_low_q             <= 'h0;
        result_high_q            <= 'h0;
    end else if (flush_mul_i | (~instruction_0_q.valid) | int_32_0_q) begin
        instruction_1_q          <= 'h0;
        type_1_q                 <= 3'b111;
        int_32_1_q               <= 1'b0;
        neg_def_1_q              <= 1'b0;
        result_low_q             <= 'h0;
        result_high_q            <= 'h0;
    end else begin
        instruction_1_q          <= instruction_0_q;
        type_1_q                 <= type_0_q;
        int_32_1_q               <= int_32_0_q;
        neg_def_1_q              <= neg_def_0_q;
        result_low_q             <= result_low_d;
        result_high_q            <= result_high_d;
    end
end

// 64-bit multiplication MUL
assign result_128 = trunc_129_128({32'b0,result_low_q} + {result_high_q[95:0],32'b0});
// Convert if the result is negative
assign result_128_def = neg_def_1_q ? trunc_129_128(~result_128 + 128'b1) : result_128;

// Select correct word
always_comb begin
    case (type_1_q)
        3'b000: begin  // Multiply word, Low part, Signed - MUL , MULW
            result_64 = result_128_def[63:0];
        end
        3'b001: begin  // Multiply word, High part, Signed - MULH
            result_64 = result_128_def[127:64];
        end
        3'b010: begin  // Multiply word, High part, SignedxUnsigned - MULHSU
            result_64 =  result_128_def[127:64];
        end
        3'b011: begin  //  Multiply word, High part, Unsigned Unsigned MULHU
            result_64 = result_128_def[127:64];
        end

assign instruction_s2.pc            = instruction_1_q.pc;
assign instruction_s2.bpred         = instruction_1_q.bpred;
assign instruction_s2.rs1           = instruction_1_q.rs1;
assign instruction_s2.rd            = instruction_1_q.rd;
assign instruction_s2.regfile_we    = instruction_1_q.regfile_we;
assign instruction_s2.instr_type    = instruction_1_q.instr_type;
assign instruction_s2.stall_csr_fence = instruction_1_q.stall_csr_fence;
assign instruction_s2.csr_addr      = instruction_1_q.result[CSR_ADDR_SIZE-1:0];
assign instruction_s2.prd           = instruction_1_q.prd;
assign instruction_s2.checkpoint_done = instruction_1_q.checkpoint_done;
assign instruction_s2.chkp          = instruction_1_q.chkp;
assign instruction_s2.gl_index      = instruction_1_q.gl_index;
assign instruction_s2.branch_taken  = 1'b0;
assign instruction_s2.result_pc     = 0;
assign instruction_s2.result        = result_64;
assign instruction_s2.ex            = instruction_1_q.ex;
assign instruction_s2.vl            = instruction_1_q.vl;
assign instruction_s2.sew            = instruction_1_q.sew;
assign instruction_s2.lmul          = instruction_1_q.lmul;
`ifdef SIM_KONATA_DUMP
assign instruction_s2.id            = instruction_1_q.id;
`endif
assign instruction_s2.fp_status     = 'h0;
assign instruction_s2.mem_type      = instruction_1_q.mem_type;

//--------------------------------------------------------------------------------------------------
//----- MUX SELECTS OUTPUT -------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------

always_comb begin
    instruction_o = (int_32_0_q & instruction_0_q.valid ) ? instruction_s1 : instruction_s2;
end

endmodule

        default: begin
            result_64 = 64'b0;
        end
    endcase


/*
  Calculates a mod n, using barret reduction.
  Can use either karatsuba multiplier or accumlate - multiply
  for the multiplications.

  Copyright (C) 2019  Benjamin Devlin and Zcash Foundation

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

module barret_mod #(
  parameter                OUT_BITS = 256,
  parameter                CTL_BITS = 8,
  parameter                IN_BITS = 512,
  parameter [OUT_BITS-1:0] P = 256'hFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141,
  parameter                K = OUT_BITS,
  parameter                MULTIPLIER = "EXTERNAL" // [ACCUM_MULT || KARATSUBA || EXTERNAL]
)(
  input                       i_clk,
  input                       i_rst,
  input [IN_BITS-1:0]         i_dat,
  input                       i_val,
  input [CTL_BITS-1:0]        i_ctl,
  output logic [CTL_BITS-1:0] o_ctl,
  output logic                o_rdy,
  output logic [OUT_BITS-1:0] o_dat,
  output logic                o_val,
  input                       i_rdy,
  if_axi_stream.source        o_mult_if,
  if_axi_stream.sink          i_mult_if
);

localparam                   MAX_IN_BITS = 2*K;
localparam [MAX_IN_BITS:0]   U = (1 << (2*K)) / P;
logic [2:0][CTL_BITS-1:0] ctl_r;

if_axi_stream #(.DAT_BITS(2*(OUT_BITS+2))) mult_in_if(i_clk);
if_axi_stream #(.DAT_BITS(2*(OUT_BITS+2))) mult_out_if(i_clk);

logic [MAX_IN_BITS:0] c1, c2, c3, c4, P_;
logic c4_grt;

always_comb begin
  P_ = {{(MAX_IN_BITS-OUT_BITS-1){1'b0}}, P};
  c4_grt  = c4 >= P_;
end

typedef enum {IDLE, S0, S1, S2, FINISHED, WAIT_MULT} state_t;
state_t state, prev_state;

always_ff @ (posedge i_clk) begin
  if (i_rst) begin
    o_rdy <= 0;
    o_dat <= 0;
    o_val <= 0;
    state <= IDLE;
    prev_state <= IDLE;
    c1 <= 0;
    c2 <= 0;
    c3 <= 0;
    c4 <= 0;
    mult_in_if.reset_source();
    mult_out_if.rdy <= 1;
    o_ctl <= 0;
    ctl_r <= 0;
  end else begin
    mult_out_if.rdy <= 1;
    case (state)
      {IDLE}: begin
        o_rdy <= 1;
        o_val <= 0;
        c4 <= i_dat;
        if (i_val && o_rdy) begin
          o_rdy <= 0;
          state <= WAIT_MULT;
          mult_in_if.val <= 1;
          ctl_r[0] <= i_ctl;
          mult_in_if.dat[0 +: OUT_BITS+1] <= i_dat >> (K-1);
          mult_in_if.dat[OUT_BITS+1 +: OUT_BITS+1] <= U;
          prev_state <= S0;
        end
      end
      {S0}: begin
        c3 <= c2 >> (K + 1);
        state <= S1;
        ctl_r[1] <= ctl_r[0];
      end
      {S1}: begin
        mult_in_if.val <= 1;
        mult_in_if.dat[0 +: OUT_BITS+1] <= c3;
        mult_in_if.dat[OUT_BITS+1 +: OUT_BITS+1] <= P;
        state <= WAIT_MULT;
        prev_state <= S2;
        ctl_r[2] <= ctl_r[1];
      end
      {S2}: begin
        if (c4_grt) begin
          c4 <= c4 - P_;
          state <= S2;
        end else begin
          state <= FINISHED;
          o_dat <= c4;
          o_val <= 1;
          o_ctl <= ctl_r[2];
        end
      end
      {FINISHED}: begin
        if (o_val && i_rdy) begin
          o_val <= 0;
          state <= IDLE;
        end
      end
      // In this state we are waiting for a multiply to be finished
      {WAIT_MULT}: begin
        if (mult_in_if.val && mult_in_if.rdy) mult_in_if.val <= 0;
        if (mult_out_if.rdy && mult_out_if.val) begin
          state <= prev_state;

        state <= IDLE;
      end
    endcase
  end
end

// Do the multiplications
generate
  if (MULTIPLIER == "ACCUM_MULT") begin: MULTIPLIER_GEN
    accum_mult # (
      .BITS_A  ( OUT_BITS +8 ),
      .LEVEL_A ( 6          ),
      .LEVEL_B ( 4          )
    )
    accum_mult (
      .i_clk ( i_clk ),
      .i_rst ( i_rst ),
      .i_dat_a ({7'd0, mult_in_if.dat[0 +: OUT_BITS+1]}) ,
      .i_dat_b ({7'd0, mult_in_if.dat[OUT_BITS+1 +: OUT_BITS+1]}),
      .i_val ( mult_in_if.val ),
      .o_rdy ( mult_in_if.rdy ),
      .o_dat ( mult_out_if.dat ),
      .o_val ( mult_out_if.val ),
      .i_rdy ( mult_out_if.rdy )
    );
  end else if (MULTIPLIER == "KARATSUBA") begin
    localparam LEVEL = 2;

    karatsuba_ofman_mult # (
      .BITS  ( OUT_BITS + 8 ),
      .LEVEL ( LEVEL        )
    )
    karatsuba_ofman_mult (
      .i_clk  ( i_clk                 ),
      .i_rst ( i_rst),
      .i_val ( mult_in_if.val ),
      .i_ctl (),
      .i_rdy( mult_out_if.rdy ),
      .o_rdy (mult_in_if.rdy),
      .o_val(mult_out_if.val),
      .i_dat_a( {7'd0, mult_in_if.dat[0 +: OUT_BITS+1]}),
      .i_dat_b( {7'd0, mult_in_if.dat[OUT_BITS+1 +: OUT_BITS+1]} ),
      .o_dat  ( mult_out_if.dat       )
    );

  always_comb begin
    o_mult_if.val = 0;
    i_mult_if.rdy = 0;
    end

  end else if (MULTIPLIER == "EXTERNAL") begin
    always_comb begin
      o_mult_if.val = mult_in_if.val;
      o_mult_if.dat = mult_in_if.dat;
      o_mult_if.sop = mult_in_if.sop;
      o_mult_if.eop = mult_in_if.eop;
      o_mult_if.err = mult_in_if.err;
      o_mult_if.mod = mult_in_if.mod;
      o_mult_if.ctl = mult_in_if.ctl;
      mult_in_if.rdy = o_mult_if.rdy;

      mult_out_if.val = i_mult_if.val;
      mult_out_if.dat = i_mult_if.dat;
      mult_out_if.sop = i_mult_if.sop;
      mult_out_if.eop = i_mult_if.eop;
      mult_out_if.err = i_mult_if.err;
      mult_out_if.mod = i_mult_if.mod;
      mult_out_if.ctl = i_mult_if.ctl;
      i_mult_if.rdy = mult_out_if.rdy;
    end
  end else
    $fatal(1, "%m ERROR: Unknown multiplier type [%s] in barret_mod.sv", MULTIPLIER);
endgenerate
initial assert (IN_BITS <= MAX_IN_BITS) else $fatal(1, "%m ERROR: IN_BITS[%d] > MAX_IN_BITS[%d] in barret_mod", IN_BITS, MAX_IN_BITS);

endmodule

          case(prev_state)
            S0: c2 <= mult_out_if.dat;
            S2: c4 <= c4 - mult_out_if.dat;
            default: begin end
          endcase


/*
  This module is the top level for the BLS12-381 coprocessor.
  Runs on instruction memory and has access to slot memory.

  Copyright (C) 2019  Benjamin Devlin and Zcash Foundation

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

module bls12_381_top
  import bls12_381_pkg::*;
#(
  parameter USE_KARATSUBA = "YES"
)(
  input i_clk, i_rst,
  // Only tx interface is used to send messages to SW on a SEND-INTERRUPT instruction
  if_axi_stream.source tx_if,
  // User access to the instruction, data, and config
  if_axi_lite.sink     axi_lite_if
);

localparam DAT_BITS = bls12_381_pkg::DAT_BITS;
localparam AXI_STREAM_BYTS = 8;

parameter type FE_TYPE   = bls12_381_pkg::fe_t;
parameter type FE2_TYPE  = bls12_381_pkg::fe2_t;
parameter type FE6_TYPE  = bls12_381_pkg::fe6_t;
parameter type FE12_TYPE = bls12_381_pkg::fe12_t;
parameter P              = bls12_381_pkg::P;

// Used for sending interrupts back to SW
import zcash_fpga_pkg::bls12_381_interrupt_rpl_t;
import zcash_fpga_pkg::bls12_381_interrupt_rpl;
bls12_381_interrupt_rpl_t interrupt_rpl;
enum {WAIT_FIFO, SEND_HDR, SEND_DATA} interrupt_state;
logic [7:0] interrupt_hdr_byt;

logic [READ_CYCLE:0] inst_ram_read, data_ram_read;
logic reset_inst_ram, reset_data_ram;

logic [31:0] mult_ram_d;
logic mult_ram_we, mult_ram_se;

// Instruction RAM
if_ram #(.RAM_WIDTH(bls12_381_pkg::INST_RAM_WIDTH), .RAM_DEPTH(bls12_381_pkg::INST_RAM_DEPTH)) inst_ram_sys_if(.i_clk(i_clk), .i_rst(i_rst || reset_inst_ram));
if_ram #(.RAM_WIDTH(bls12_381_pkg::INST_RAM_WIDTH), .RAM_DEPTH(bls12_381_pkg::INST_RAM_DEPTH)) inst_ram_usr_if(.i_clk(i_clk), .i_rst(i_rst || reset_inst_ram));
inst_t curr_inst;

// Data RAM
if_ram #(.RAM_WIDTH(bls12_381_pkg::DATA_RAM_WIDTH), .RAM_DEPTH(bls12_381_pkg::DATA_RAM_DEPTH)) data_ram_sys_if(.i_clk(i_clk), .i_rst(i_rst || reset_data_ram));
if_ram #(.RAM_WIDTH(bls12_381_pkg::DATA_RAM_WIDTH), .RAM_DEPTH(bls12_381_pkg::DATA_RAM_DEPTH)) data_ram_usr_if(.i_clk(i_clk), .i_rst(i_rst || reset_data_ram));
data_t curr_data, new_data;

// Loading the fifo with slots and outputting an interrupt
if_axi_stream #(.DAT_BYTS(48)) interrupt_in_if(i_clk);
if_axi_stream #(.DAT_BYTS(8)) interrupt_out_if(i_clk);
if_axi_stream #(.DAT_BYTS(3)) idx_in_if(i_clk);
if_axi_stream #(.DAT_BYTS(3)) idx_out_if(i_clk);

// Point multiplication
logic [1:0] pair_mode;
fe_t  pair_key;
if_axi_stream #(.DAT_BITS($bits(FE_TYPE)), .CTL_BITS(CTL_BITS)) mult_pt_if (i_clk);

if_axi_stream #(.DAT_BITS(2*$bits(bls12_381_pkg::fp2_jb_point_t))) add_i_if(i_clk);
if_axi_stream #(.DAT_BITS($bits(bls12_381_pkg::fp2_jb_point_t)))   add_o_if(i_clk);
if_axi_stream #(.DAT_BITS($bits(bls12_381_pkg::fp2_jb_point_t)))   dbl_i_if(i_clk);
if_axi_stream #(.DAT_BITS($bits(bls12_381_pkg::fp2_jb_point_t)))   dbl_o_if(i_clk);

localparam CTL_BITS = 128;

if_axi_stream #(.DAT_BITS(2*$bits(FE_TYPE)), .CTL_BITS(CTL_BITS)) mul_in_if  [2:0] (i_clk);
if_axi_stream #(.DAT_BITS($bits(FE_TYPE)), .CTL_BITS(CTL_BITS))   mul_out_if [2:0] (i_clk);
if_axi_stream #(.DAT_BITS(2*$bits(FE_TYPE)), .CTL_BITS(CTL_BITS)) add_in_if        (i_clk);
if_axi_stream #(.DAT_BITS($bits(FE_TYPE)), .CTL_BITS(CTL_BITS))   add_out_if       (i_clk);
if_axi_stream #(.DAT_BITS(2*$bits(FE_TYPE)), .CTL_BITS(CTL_BITS)) sub_in_if        (i_clk);
if_axi_stream #(.DAT_BITS($bits(FE_TYPE)), .CTL_BITS(CTL_BITS))   sub_out_if       (i_clk);

if_axi_stream #(.DAT_BITS($bits(FE_TYPE)), .CTL_BITS(CTL_BITS))   inv_fe_o_if      (i_clk);
if_axi_stream #(.DAT_BITS($bits(FE_TYPE)), .CTL_BITS(CTL_BITS))   inv_fe_i_if      (i_clk);
if_axi_stream #(.DAT_BITS($bits(FE_TYPE)), .CTL_BITS(CTL_BITS))   inv_fe2_o_if     (i_clk);
if_axi_stream #(.DAT_BITS($bits(FE_TYPE)), .CTL_BITS(CTL_BITS))   inv_fe2_i_if     (i_clk);

if_axi_stream #(.DAT_BITS(2*$bits(FE_TYPE)), .CTL_BITS(CTL_BITS)) mul_fe12_o_if     (i_clk);
if_axi_stream #(.DAT_BITS($bits(FE_TYPE)), .CTL_BITS(CTL_BITS))   mul_fe12_i_if     (i_clk);

if_axi_stream #(.DAT_BITS($bits(FE_TYPE))) pair_i_af_if  (i_clk);
if_axi_stream #(.DAT_BITS($bits(FE_TYPE))) pair_o_res_if (i_clk);

logic [31:0] new_inst_pt;
logic        new_inst_pt_val, new_inst_pt_val_l;
logic        reset_done_inst, reset_done_data;

logic [7:0] cnt;
integer unsigned pt_size;

always_comb begin
  curr_inst = inst_ram_sys_if.q;
  curr_data = data_ram_sys_if.q;
  data_ram_sys_if.d = new_data;
end

code_t inst_state;
point_type_t pt_l;

logic [31:0] last_inst_cnt, curr_inst_pt;

always_ff @ (posedge i_clk) begin
  if (i_rst) begin
    inst_ram_sys_if.reset_source();
    data_ram_sys_if.we <= 0;
    data_ram_sys_if.a <= 0;
    data_ram_sys_if.re <= 1;
    data_ram_sys_if.en <= 1;
    inst_ram_read <= 0;
    data_ram_read <= 0;
    cnt <= 0;
    inv_fe_o_if.reset_source();
    inv_fe_i_if.rdy <= 0;
    inv_fe2_o_if.reset_source();
    inv_fe2_i_if.rdy <= 0;
    inst_state <= NOOP_WAIT;
    pt_l <= SCALAR;
    new_data <= 0;
    pt_size <= 0;
    idx_in_if.reset_source();
    interrupt_in_if.reset_source();
    last_inst_cnt <= 0;
    pair_o_res_if.rdy <= 0;

    new_inst_pt_val_l <= 0;

    mul_in_if[1].reset_source();
    add_in_if.reset_source();
    sub_in_if.reset_source();

    mul_out_if[1].rdy <= 0;
    add_out_if.rdy <= 0;
    sub_out_if.rdy <= 0;

    pair_i_af_if.reset_source();

    pair_mode <= 0;
    pair_key <= 0;
    mult_pt_if.rdy <= 0;

    mul_fe12_o_if.reset_source();
    mul_fe12_i_if.rdy <= 0;

  end else begin

    mul_in_if[1].sop <= 1;
    mul_in_if[1].eop <= 1;
    add_in_if.sop <= 1;
    add_in_if.eop <= 1;
    sub_in_if.sop <= 1;
    sub_in_if.eop <= 1;

    new_inst_pt_val_l <= new_inst_pt_val || new_inst_pt_val_l; // Latch this pulse if we want to update instruction pointer

    inst_ram_sys_if.re <= 1;
    inst_ram_sys_if.en <= 1;
    inst_ram_read <= inst_ram_read << 1;

    data_ram_sys_if.re <= 1;
    data_ram_sys_if.en <= 1;
    data_ram_sys_if.we <= 0;
    data_ram_read <= data_ram_read << 1;

    if (inv_fe_o_if.rdy) inv_fe_o_if.val <= 0;
    if (inv_fe2_o_if.rdy) inv_fe2_o_if.val <= 0;
    if (add_in_if.rdy) add_in_if.val <= 0;
    if (sub_in_if.rdy) sub_in_if.val <= 0;
    if (mul_in_if[1].rdy) mul_in_if[1].val <= 0;
    if (pair_i_af_if.rdy) pair_i_af_if.val <= 0;
    if (mul_fe12_o_if.rdy) mul_fe12_o_if.val <= 0;

    mult_pt_if.rdy <= 1;
    mul_fe12_i_if.rdy <= 1;

    if (idx_in_if.val && idx_in_if.rdy) idx_in_if.val <= 0;
    if (interrupt_in_if.val && interrupt_in_if.rdy) interrupt_in_if.val <= 0;

    last_inst_cnt <= last_inst_cnt + 1;

    case(inst_state)
      NOOP_WAIT: begin
        last_inst_cnt <= last_inst_cnt;
        // Wait in this state
        get_next_inst();
      end
      JUMP: begin
        last_inst_cnt <= last_inst_cnt;
        task_jump();
      end
      JUMP_IF_EQ: begin
        last_inst_cnt <= last_inst_cnt;
        task_jump_if_eq();
      end
      JUMP_NONZERO_SUB: begin
        last_inst_cnt <= last_inst_cnt;
        task_jump_nonzero_sub();
      end
      COPY_REG: begin
        last_inst_cnt <= last_inst_cnt;
        task_copy_reg();
      end
      INV_ELEMENT: begin
        if (cnt == 0) last_inst_cnt <= 0;
        task_inv_element();
      end
      MUL_ELEMENT: begin
        if (cnt == 0) last_inst_cnt <= 0;
        task_mul_element();
      end
      SUB_ELEMENT: begin
        if (cnt == 0) last_inst_cnt <= 0;
        task_sub_element();
      end
      ADD_ELEMENT: begin
        if (cnt == 0) last_inst_cnt <= 0;
        task_add_element();
      end
      SEND_INTERRUPT: begin
        last_inst_cnt <= last_inst_cnt;
        task_send_interrupt();
      end
      POINT_MULT: begin
        if (cnt == 0) last_inst_cnt <= 0;
        task_point_mult();
      end
      ATE_PAIRING: begin
        if (cnt == 0) last_inst_cnt <= 0;
        pair_mode <= 0;
        task_pairing();
      end
      MILLER_LOOP: begin
        if (cnt == 0) last_inst_cnt <= 0;
        pair_mode <= 2;
        task_pairing();
      end
      FINAL_EXP: begin
        if (cnt == 0) last_inst_cnt <= 0;
        task_final_exp();
      end
      default: get_next_inst();
    endcase

  end
end

bls12_381_axi_bridge bls12_381_axi_bridge (
  .i_clk ( i_clk ),
  .i_rst ( i_rst ),
  .axi_lite_if        ( axi_lite_if     ),
  .data_ram_if        ( data_ram_usr_if ),
  .inst_ram_if        ( inst_ram_usr_if ),
  .i_curr_inst_pt     ( curr_inst_pt    ),
  .i_last_inst_cnt    ( last_inst_cnt   ),
  .i_reset_done       ( reset_done_data && reset_done_inst ),
  .o_new_inst_pt      ( new_inst_pt     ),
  .o_new_inst_pt_val  ( new_inst_pt_val ),
  .o_reset_inst_ram   ( reset_inst_ram  ),
  .o_reset_data_ram   ( reset_data_ram  ),
  .o_ram_d            ( mult_ram_d      ),
  .o_ram_we           ( mult_ram_we     ),
  .o_ram_se           ( mult_ram_se     )
);

always_comb begin
  curr_inst_pt = 0;
  curr_inst_pt = inst_ram_sys_if.a;
end

uram_reset #(
  .RAM_WIDTH(bls12_381_pkg::INST_RAM_WIDTH),
  .RAM_DEPTH(bls12_381_pkg::INST_RAM_DEPTH),
  .PIPELINES( READ_CYCLE - 2 )
)
inst_uram_reset (
  .a ( inst_ram_usr_if ),
  .b ( inst_ram_sys_if ),
  .o_reset_done ( reset_done_inst )
);

uram_reset #(
  .RAM_WIDTH(bls12_381_pkg::DATA_RAM_WIDTH),
  .RAM_DEPTH(bls12_381_pkg::DATA_RAM_DEPTH),
  .PIPELINES( READ_CYCLE - 2 )
)
data_uram_reset (
  .a ( data_ram_usr_if ),
  .b ( data_ram_sys_if ),
  .o_reset_done ( reset_done_data )
);

bls12_381_pairing_wrapper #(
  .CTL_BITS    ( CTL_BITS ),
  .OVR_WRT_BIT ( 0        )
)
bls12_381_pairing_wrapper (
  .i_clk ( i_clk ),
  .i_rst ( i_rst ),
  .i_pair_af_if ( pair_i_af_if ),
  .i_mode  ( pair_mode ),
  .i_key   ( pair_key  ),
  .o_fe12_if     ( pair_o_res_if ),
  .o_p_jb_if     ( mult_pt_if    ),
  .o_mul_fe_if   ( mul_in_if[0]  ),
  .i_mul_fe_if   ( mul_out_if[0] ),
  .i_mul_fe12_if ( mul_fe12_o_if ),
  .o_mul_fe12_if ( mul_fe12_i_if ),
  .o_inv_fe2_if  ( inv_fe2_i_if  ),
  .i_inv_fe2_if  ( inv_fe2_o_if  ),
  .o_inv_fe_if   ( inv_fe_i_if   ),
  .i_inv_fe_if   ( inv_fe_o_if   )
);

resource_share # (
  .NUM_IN       ( 2                ),
  .DAT_BITS     ( 2*$bits(FE_TYPE) ),
  .CTL_BITS     ( CTL_BITS         ),
  .OVR_WRT_BIT  ( 120              ),
  .PIPELINE_IN  ( 1                ),
  .PIPELINE_OUT ( 0                )
)
resource_share_mul (
  .i_clk ( i_clk ),
  .i_rst ( i_rst ),
  .i_axi ( mul_in_if[1:0]  ),
  .o_res ( mul_in_if[2]    ),
  .i_res ( mul_out_if[2]   ),
  .o_axi ( mul_out_if[1:0] )
);

generate
  if (USE_KARATSUBA == "YES") begin: GEN_KARATSUBA
    ec_fp_mult_mod #(
      .P             ( P        ),
      .KARATSUBA_LVL ( 3        ),
      .CTL_BITS      ( CTL_BITS )
    )
    ec_fp_mult_mod (
      .i_clk( i_clk ),
      .i_rst( i_rst ),
      .i_mul ( mul_in_if[2]  ),
      .o_mul ( mul_out_if[2] )
    );
  end else begin
    accum_mult_mod #(
      .DAT_BITS ( $bits(FE_TYPE) ),
      .MODULUS  ( P ),
      .CTL_BITS ( CTL_BITS ),
      .A_DSP_W  ( 26 ),
      .B_DSP_W  ( 17 ),
      .GRID_BIT ( 64 ),
      .RAM_A_W  ( 10 ),
      .RAM_D_W  ( 32 )
    )
    accum_mult_mod (
      .i_clk ( i_clk ),
      .i_rst ( i_rst ),
      .i_mul ( mul_in_if[2]  ),
      .o_mul ( mul_out_if[2] ),
      .i_ram_d  ( mult_ram_d ),
      .i_ram_we ( mult_ram_we ),
      .i_ram_se ( mult_ram_se )
    );
  end
endgenerate

adder_pipe # (
  .P        ( P        ),
  .CTL_BITS ( CTL_BITS ),
  .LEVEL    ( 2        )
)
adder_pipe (
  .i_clk ( i_clk ),
  .i_rst ( i_rst ),
  .i_add ( add_in_if  ),
  .o_add ( add_out_if )
);

subtractor_pipe # (
  .P        ( P        ),
  .CTL_BITS ( CTL_BITS ),
  .LEVEL    ( 2        )
)
subtractor_pipe (
  .i_clk ( i_clk ),
  .i_rst ( i_rst ),
  .i_sub ( sub_in_if  ),
  .o_sub ( sub_out_if )
);

// Tasks for each of the different instructions

task get_next_inst();
  if(inst_ram_read == 0) begin
    inst_ram_sys_if.a <=  new_inst_pt_val_l ? new_inst_pt : inst_state == NOOP_WAIT ? inst_ram_sys_if.a : inst_ram_sys_if.a + 1;
    inst_ram_read[0] <= 1;
    if (new_inst_pt_val_l) new_inst_pt_val_l <= 0;
  end
  if (inst_ram_read[READ_CYCLE]) begin
    inst_state <= curr_inst.code;
    cnt <= 0;
  end
endtask

task task_sub_element();
  case(cnt)
    0: begin
      sub_out_if.rdy <= 1;
      data_ram_sys_if.a <= curr_inst.a;
      data_ram_read[0] <= 1;
      cnt <= 1;
    end
    1: begin
      if (data_ram_read[READ_CYCLE]) begin
        sub_in_if.dat[0 +: $bits(fe_t)] <= curr_data.dat;
        pt_l <= curr_data.pt;
        data_ram_sys_if.a <=  curr_inst.b;
        data_ram_read[0] <= 1;
        cnt <= 2;
      end
    end
    2: begin
      if (data_ram_read[READ_CYCLE]) begin
        sub_in_if.dat[$bits(fe_t) +: $bits(fe_t)] <= curr_data.dat;
        sub_in_if.val <= 1;
      end
      if (sub_out_if.val && sub_out_if.rdy) begin
        data_ram_sys_if.a <=  curr_inst.c;
        new_data.dat <= sub_out_if.dat;
        new_data.pt <= pt_l;
        data_ram_sys_if.we <= 1;
        cnt <= 5;
        if (pt_l == FE2 || pt_l == FP2_JB || pt_l == FP2_AF) begin
          // FE2 requires extra logic
          cnt <= 3;
        end
      end
    end
    3: begin
      if (!(|data_ram_read)) begin
        data_ram_sys_if.a <= curr_inst.a + 1;
        data_ram_read[0] <= 1;
      end
      if (data_ram_read[READ_CYCLE]) begin
        sub_in_if.dat[0 +: $bits(fe_t)] <= curr_data.dat;
        pt_l <= curr_data.pt;
        data_ram_sys_if.a <=  curr_inst.b + 1;
        data_ram_read[0] <= 1;
        cnt <= 4;
      end
    end
    4: begin
      if (data_ram_read[READ_CYCLE]) begin
        sub_in_if.dat[$bits(fe_t) +: $bits(fe_t)] <= curr_data.dat;
        sub_in_if.val <= 1;
      end
      if (sub_out_if.val && sub_out_if.rdy) begin
        data_ram_sys_if.a <=  curr_inst.c + 1;
        new_data.dat <= sub_out_if.dat;
        new_data.pt <= pt_l;
        data_ram_sys_if.we <= 1;
        cnt <= 5;
      end
    end
    5: begin
      get_next_inst();
    end
  endcase
endtask;

task task_add_element();
  case(cnt)
    0: begin
      add_out_if.rdy <= 1;
      data_ram_sys_if.a <= curr_inst.a;
      data_ram_read[0] <= 1;
      cnt <= cnt + 1;
    end
    1: begin
      if (data_ram_read[READ_CYCLE]) begin
        add_in_if.dat[0 +: $bits(fe_t)] <= curr_data.dat;
        pt_l <= curr_data.pt;
        data_ram_sys_if.a <=  curr_inst.b;
        data_ram_read[0] <= 1;
        cnt <= 2;
      end
    end
    2: begin
      if (data_ram_read[READ_CYCLE]) begin
        add_in_if.dat[$bits(fe_t) +: $bits(fe_t)] <= curr_data.dat;
        add_in_if.val <= 1;
      end
      if (add_out_if.val && add_out_if.rdy) begin
        data_ram_sys_if.a <=  curr_inst.c;
        new_data.dat <= add_out_if.dat;
        new_data.pt <= pt_l;
        data_ram_sys_if.we <= 1;
        cnt <= 5;
        if (pt_l == FE2 || pt_l == FP2_JB || pt_l == FP2_AF) begin
          // FE2 requires extra logic
          cnt <= 3;
        end
      end
    end
    3: begin
      if (!(|data_ram_read)) begin
        data_ram_sys_if.a <= curr_inst.a + 1;
        data_ram_read[0] <= 1;
      end
      if (data_ram_read[READ_CYCLE]) begin
        add_in_if.dat[0 +: $bits(fe_t)] <= curr_data.dat;
        pt_l <= curr_data.pt;
        data_ram_sys_if.a <=  curr_inst.b + 1;
        data_ram_read[0] <= 1;
        cnt <= 4;
      end
    end
    4: begin
      if (data_ram_read[READ_CYCLE]) begin
        add_in_if.dat[$bits(fe_t) +: $bits(fe_t)] <= curr_data.dat;
        add_in_if.val <= 1;
      end
      if (add_out_if.val && add_out_if.rdy) begin
        data_ram_sys_if.a <=  curr_inst.c + 1;
        new_data.dat <= add_out_if.dat;
        new_data.pt <= pt_l;
        data_ram_sys_if.we <= 1;
        cnt <= 5;
      end
    end
    5: begin
      get_next_inst();
    end
  endcase
endtask;

task task_mul_element();
  case(cnt)
    0: begin
      mul_out_if[1].rdy <= 1;
      data_ram_sys_if.a <= curr_inst.a;
      data_ram_read[0] <= 1;
      cnt <= cnt + 1;
    end
    1: begin
      if (data_ram_read[READ_CYCLE]) begin
        mul_in_if[1].dat[0 +: $bits(fe_t)] <= curr_data.dat;
        pt_l <= curr_data.pt;
        data_ram_sys_if.a <=  curr_inst.b;
        data_ram_read[0] <= 1;
        cnt <= 2;
        if (curr_data.pt == FE12) begin
          cnt <= 8;
          data_ram_sys_if.a <=  curr_inst.a;
        end
      end
    end
    2: begin
      if (data_ram_read[READ_CYCLE]) begin
        mul_in_if[1].dat[$bits(fe_t) +: $bits(fe_t)] <= curr_data.dat;
        mul_in_if[1].val <= 1;
        mul_in_if[1].ctl <= 0;
        if (pt_l == FE2 || pt_l == FP2_JB || pt_l == FP2_AF) begin
          data_ram_sys_if.a <= curr_inst.a + 1;
          data_ram_read[0] <= 1;
          mul_out_if[1].rdy <= 0;
          // FE2 requires extra logic
          cnt <= 3;
        end
      end
      if (mul_out_if[1].val && mul_out_if[1].rdy) begin
        data_ram_sys_if.a <=  curr_inst.c;
        new_data.dat <= mul_out_if[1].dat;
        new_data.pt <= pt_l;
        data_ram_sys_if.we <= 1;
        cnt <= 33;
      end
    end
    3: begin
      if (data_ram_read[READ_CYCLE]) begin
         mul_in_if[1].dat[0 +: $bits(fe_t)] <= curr_data.dat;
         mul_in_if[1].val <= 1;
         mul_in_if[1].ctl <= 3;
         data_ram_sys_if.a <= curr_inst.b + 1;
         data_ram_read[0] <= 1;
         cnt <= 4;
      end
    end
    4: begin
      if (data_ram_read[READ_CYCLE]) begin
         mul_in_if[1].dat[$bits(fe_t) +: $bits(fe_t)] <= curr_data.dat;
         mul_in_if[1].val <= 1;
         mul_in_if[1].ctl <= 1;

    5: begin
      if (data_ram_read[READ_CYCLE]) begin
         mul_in_if[1].dat[0 +: $bits(fe_t)] <= curr_data.dat;
         mul_in_if[1].val <= 1;
         mul_in_if[1].ctl <= 2;
         mul_out_if[1].rdy <= 1;
         cnt <= 6;
      end
    end
    6: begin
      sub_out_if.rdy <= 1;
      if (mul_out_if[1].val && mul_out_if[1].rdy) begin
        case(mul_out_if[1].ctl)
          0: begin
            sub_in_if.dat[0 +: $bits(fe_t)] <= mul_out_if[1].dat;
          end
          1: begin
            sub_in_if.dat[$bits(fe_t) +: $bits(fe_t)] <= mul_out_if[1].dat;
            sub_in_if.val <= 1;
          end
          2: begin
            add_in_if.dat[0 +: $bits(fe_t)] <= mul_out_if[1].dat;
            add_in_if.val <= 1;
          end
          3: begin
            add_in_if.dat[$bits(fe_t) +: $bits(fe_t)] <= mul_out_if[1].dat;
          end
        endcase
      end

      if (sub_out_if.val && sub_out_if.rdy) begin
        new_data.dat <= sub_out_if.dat;
        new_data.pt <= pt_l;
        data_ram_sys_if.we <= 1;
        data_ram_sys_if.a <=  curr_inst.c;
        add_out_if.rdy <= 1;
      end
      if (add_out_if.val && add_out_if.rdy) begin
        new_data.dat <= add_out_if.dat;
        new_data.pt <= pt_l;
        data_ram_sys_if.we <= 1;
        data_ram_sys_if.a <=  curr_inst.c + 1;
        cnt <= 33;
      end
    end
    // FE12 multiplication
    8,9,10,11,12,13,14,15,16,17,18,19,
    20,21,22,23,24,25,26,27,28,29,30,31: begin
      mul_fe12_i_if.rdy <= 0;

      if (|data_ram_read[READ_CYCLE:1]== 0 && (~mul_fe12_o_if.val || (mul_fe12_o_if.val && mul_fe12_o_if.rdy))) begin
        if (data_ram_read[0]) begin
          data_ram_read[0] <= 1;
          data_ram_sys_if.a <= curr_inst.b + ((cnt-8)/2);
        end else begin
          data_ram_read[0] <= 1;
          data_ram_sys_if.a <= curr_inst.a + ((cnt-8)/2);
        end
      end

      if (data_ram_read[READ_CYCLE]) begin
       cnt <= cnt + 1;
        if (cnt % 2 == 1) begin
          mul_fe12_o_if.sop <= cnt == 9;
          mul_fe12_o_if.eop <= cnt == 31;
          mul_fe12_o_if.val <= 1;
          mul_fe12_o_if.dat[$bits(fe_t) +: $bits(fe_t)] <= curr_data.dat;
        end else begin
          mul_fe12_o_if.dat[0 +: $bits(fe_t)] <= curr_data.dat;
        end
      end
    end
    32: begin
      mul_fe12_i_if.rdy <= 1;
      if (mul_fe12_i_if.val && mul_fe12_i_if.rdy) begin
        if (mul_fe12_i_if.sop)
          data_ram_sys_if.a <= curr_inst.c;
        else
          data_ram_sys_if.a <= data_ram_sys_if.a + 1;
        data_ram_sys_if.we <= 1;
        new_data.dat <= mul_fe12_i_if.dat;
        new_data.pt <= pt_l;
        if (mul_fe12_i_if.eop) cnt <= cnt + 1;
      end
    end
    33: begin
      get_next_inst();
    end
  endcase
endtask;

task task_copy_reg();
  case(cnt)
    0: begin
      data_ram_sys_if.a <= curr_inst.a;
      data_ram_read[0] <= 1;
      cnt <= cnt + 1;
    end
    1: begin
      if (data_ram_read[READ_CYCLE]) begin
        data_ram_sys_if.a <=  curr_inst.b;
        new_data <= curr_data;
        data_ram_sys_if.we <= 1;
        cnt <= cnt + 1;
      end
    end
    2: begin
      get_next_inst();
    end
  endcase
endtask

task task_jump();
  case(cnt)
    0: begin
      inst_ram_sys_if.a <= curr_inst.a;
      inst_ram_read[0] <= 1;
      cnt <= cnt + 1;
    end
    1: begin
      get_next_inst();
    end
  endcase
endtask

task task_jump_if_eq();
  case(cnt)
    0: begin
      data_ram_sys_if.a <= curr_inst.b;
      data_ram_read[0] <= 1;
      cnt <= cnt + 1;
    end
    1: begin
      if (data_ram_read[READ_CYCLE]) begin
        data_ram_sys_if.a <= curr_inst.c;
        new_data <= curr_data;
        data_ram_read[0] <= 1;
        cnt <= cnt + 1;
      end
    end
    2: begin
      if (data_ram_read[READ_CYCLE]) begin
        if (new_data.dat[63:0] == curr_data.dat[63:0])
          inst_ram_sys_if.a <= curr_inst.a;
        else
          inst_ram_sys_if.a <= inst_ram_sys_if.a + 1;
        inst_ram_read[0] <= 1;
        cnt <= cnt + 1;
      end
    end
    3: begin
      get_next_inst();
    end
  endcase
endtask

task task_jump_nonzero_sub();
  case(cnt)
    0: begin
      data_ram_sys_if.a <= curr_inst.b;
      data_ram_read[0] <= 1;
      cnt <= cnt + 1;
    end
    1: begin
      if (data_ram_read[READ_CYCLE]) begin
        if (curr_data.dat[63:0] != 0) begin
          inst_ram_sys_if.a <= curr_inst.a;
          new_data.pt <= curr_data.pt;
          new_data.dat[63:0] <= curr_data.dat[63:0] - 1;
          data_ram_sys_if.we <= 1;
        end else begin
          inst_ram_sys_if.a <= inst_ram_sys_if.a + 1;
        end
        inst_ram_read[0] <= 1;
        cnt <= cnt + 1;
      end
    end
    2: begin
      get_next_inst();
    end
  endcase
endtask

task task_inv_element();
  case(cnt)
    0: begin
      inv_fe_o_if.reset_source();
      inv_fe2_o_if.reset_source();
      inv_fe_i_if.rdy <= 0;
      inv_fe2_i_if.rdy <= 0;
      data_ram_sys_if.a <= curr_inst.a;
      data_ram_read[0] <= 1;
      cnt <= cnt + 1;
    end
    1: begin
      if (data_ram_read[READ_CYCLE]) begin
        // Depending on type of data
        if (curr_data.pt == FE) begin
          inv_fe_o_if.val <= 1;
          inv_fe_o_if.dat <= curr_data.dat;
          inv_fe_o_if.sop <= 1;
          inv_fe_o_if.eop <= 1;
          pt_l <= curr_data.pt;
        end else begin
          inv_fe2_o_if.dat <= curr_data.dat;
          data_ram_sys_if.a <= data_ram_sys_if.a + 1;
          data_ram_read[0] <= 1;
          inv_fe2_o_if.ctl <= 0;
          inv_fe2_o_if.val <= 1;
          inv_fe2_o_if.sop <= 1;
          inv_fe2_o_if.eop <= 0;
        end
      end
      if (inv_fe_o_if.val && inv_fe_o_if.rdy) cnt <= 2;
      if (inv_fe2_o_if.val && inv_fe2_o_if.rdy) cnt <= 3;
    end
    2: begin
      inv_fe_i_if.rdy <= 1;
      // FE element
      if (inv_fe_i_if.val && inv_fe_i_if.rdy) begin
        data_ram_sys_if.a <= curr_inst.b;
        new_data.pt <= pt_l;
        new_data.dat <= inv_fe_i_if.dat;
        data_ram_sys_if.we <= 1;
        cnt <= 5;
      end
    end
    //FE2 element
    3: begin
      if (data_ram_read[READ_CYCLE]) begin
        inv_fe2_o_if.val <= 1;
        inv_fe2_o_if.dat <= curr_data.dat;
        inv_fe2_o_if.sop <= 0;
        inv_fe2_o_if.eop <= 1;
        pt_l <= curr_data.pt;
      end
      if (inv_fe2_o_if.eop && inv_fe2_o_if.val && inv_fe2_o_if.rdy) cnt <= 4;
    end
    4: begin
      inv_fe2_i_if.rdy <= 1;
      if (inv_fe2_i_if.val && inv_fe2_i_if.rdy) begin
        data_ram_sys_if.a <= inv_fe2_i_if.sop ? curr_inst.b : data_ram_sys_if.a + 1;
        new_data.pt <= pt_l;
        new_data.dat <= inv_fe2_i_if.dat;
        data_ram_sys_if.we <= 1;
        if (inv_fe2_i_if.eop) cnt <= 5;
      end
    end
    5: begin
      get_next_inst();
    end
  endcase
endtask

task task_point_mult();
  pair_mode <= 1;
  case(cnt) inside
    0: begin
      data_ram_sys_if.a <= curr_inst.a;
      if (|data_ram_read == 0) data_ram_read[0] <= 1;
      if (data_ram_read[READ_CYCLE]) begin
        cnt <= cnt + 1;
        pair_key <= curr_data.dat;
        data_ram_sys_if.a <= curr_inst.b;
        data_ram_read[0] <= 1;
      end
    end
    1,2,3,4: begin
      if (|data_ram_read == 0 && (~pair_i_af_if.val || (pair_i_af_if.val && pair_i_af_if.rdy))) begin
        data_ram_read[0] <= 1;
        data_ram_sys_if.a <= data_ram_sys_if.a + 1;
        if (curr_data.pt == FP_AF && cnt % 2 == 0) data_ram_sys_if.a <= data_ram_sys_if.a;
      end
      if (data_ram_read[READ_CYCLE]) begin
        pair_i_af_if.val <= 1;
        pair_i_af_if.sop <= cnt == 1;
        pair_i_af_if.eop <= cnt == 4;
        pair_i_af_if.dat <= (curr_data.pt == FP_AF && cnt % 2 == 0) ? 0 : curr_data.dat;
        cnt <= cnt + 1;
        if (cnt == 1) pt_l <= curr_data.pt;
        if (cnt == 4) begin
          data_ram_sys_if.a <= curr_inst.c;
        end
      end
    end
    // Wait for result
    5,6,7,8,9,10: begin
      mult_pt_if.rdy <= 1;
      if (mult_pt_if.val) begin
         new_data.pt <= pt_l == FP_AF ? FP_JB : FP2_JB;
         new_data.dat <= mult_pt_if.dat;
         data_ram_sys_if.we <= 1;
         if (cnt > 5) data_ram_sys_if.a <= data_ram_sys_if.a + 1;
         if (pt_l == FP_AF && cnt % 2 == 0) begin // Even elements will be 0 for FP points
           data_ram_sys_if.a <= data_ram_sys_if.a;
           data_ram_sys_if.we <= 0;
         end
         cnt <= cnt + 1;
      end
    end
    11: begin
      pair_mode <= 0;
      get_next_inst();
    end
  endcase
endtask

task task_pairing();
  case(cnt) inside
    0: begin
      data_ram_sys_if.a <= curr_inst.a;
      data_ram_read[0] <= 1;
      cnt <= cnt + 1;
    end
    // Load G1 affine point
    1,2: begin
      if (data_ram_read[READ_CYCLE]) begin
        data_ram_sys_if.a <= data_ram_sys_if.a + 1;
        data_ram_read[0] <= 1;
        pair_i_af_if.dat <= curr_data.dat;
        pair_i_af_if.val <= 1;
        pair_i_af_if.sop <= cnt == 1;
        pair_i_af_if.eop <= cnt == 2;
        cnt <= cnt + 1;
        if (cnt == 2) begin
          data_ram_sys_if.a <= curr_inst.b;
        end
      end
    end
    // Load G2 affine point
    3,4,5,6: begin
      if (data_ram_read[READ_CYCLE]) begin
        data_ram_sys_if.a <= data_ram_sys_if.a + 1;
        data_ram_read[0] <= 1;
        pair_i_af_if.dat <= curr_data.dat;
        pair_i_af_if.val <= 1;
        pair_i_af_if.sop <= cnt == 3;
        pair_i_af_if.eop <= cnt == 6;
        cnt <= cnt + 1;
        if (cnt == 6) begin
          data_ram_sys_if.a <= curr_inst.c;
          pair_o_res_if.rdy <= 1;
          mult_pt_if.rdy <= 1;
        end
      end
    end
    // Wait for result
    7: begin
      if (pair_o_res_if.val || mult_pt_if.val) begin
         new_data.pt <= FE12;
         new_data.dat <= pair_o_res_if.val ? pair_o_res_if.dat : mult_pt_if.dat;
         data_ram_sys_if.we <= 1;
         if ((pair_o_res_if.val && ~pair_o_res_if.sop) ||
              (mult_pt_if.val && ~mult_pt_if.sop))
           data_ram_sys_if.a <= data_ram_sys_if.a + 1;
         if (pair_o_res_if.eop || mult_pt_if.eop) begin
           mult_pt_if.rdy <= 0;
           pair_o_res_if.rdy <= 0;
           cnt <= cnt + 1;
         end
      end
    end
    8: begin
      get_next_inst();
    end
  endcase
endtask

task task_final_exp();
  pair_mode <= 3;
  case(cnt) inside
    0: begin
      data_ram_sys_if.a <= curr_inst.a;
      data_ram_read[0] <= 1;
      cnt <= cnt + 1;
    end
    // Load FE12
    1,2,3,4,5,6,7,8,9,10,11,12: begin
      if (data_ram_read[READ_CYCLE]) begin
        data_ram_sys_if.a <= data_ram_sys_if.a + 1;
        data_ram_read[0] <= 1;
        pair_i_af_if.dat <= curr_data.dat;
        pair_i_af_if.val <= 1;
        pair_i_af_if.sop <= cnt == 1;
        pair_i_af_if.eop <= cnt == 12;
        cnt <= cnt + 1;
        if (cnt == 12) begin
          data_ram_sys_if.a <= curr_inst.b;
        end
      end
    end
    // Wait for result
    13: begin
      pair_o_res_if.rdy <= 1;
      if (pair_o_res_if.val && pair_o_res_if.rdy) begin
         new_data.pt <= FE12;
         new_data.dat <= pair_o_res_if.dat;
         data_ram_sys_if.we <= 1;
         if (~pair_o_res_if.sop) data_ram_sys_if.a <= data_ram_sys_if.a + 1;
         if (pair_o_res_if.eop) begin
           cnt <= cnt + 1;
           pair_o_res_if.rdy <= 0;
         end
      end
    end
    14: begin
      get_next_inst();
    end
  endcase
endtask

task task_send_interrupt();
  case(cnt) inside
    // Load the data
    0: begin
      interrupt_in_if.eop <= 0;
      data_ram_sys_if.a <= curr_inst.a;
      if (interrupt_state != WAIT_FIFO) begin
        // Wait here
      end else begin
        data_ram_read[0] <= 1;
        cnt <= cnt + 1;
      end
    end
    // Check what type of data it is and write index fifo
    1: begin
      if (data_ram_read[READ_CYCLE]) begin
        pt_size <= get_point_type_size(curr_data.pt);
        idx_in_if.val <= 1;
        idx_in_if.dat <= {curr_data.pt, curr_inst.b};
      end
      if (idx_in_if.val && idx_in_if.rdy) begin
        cnt <= cnt + 1;
        data_ram_read[0] <= 1;
        data_ram_sys_if.a <= curr_inst.a;
      end
    end
    // Write the slot fifo
    2: begin

      if (~interrupt_in_if.val) begin
        interrupt_in_if.dat <= curr_data.dat;
        interrupt_in_if.val <= data_ram_read[READ_CYCLE];
        interrupt_in_if.eop <= pt_size == 1;
      end

      if (interrupt_in_if.val && interrupt_in_if.rdy) begin
        pt_size <= pt_size - 1;
        interrupt_in_if.val <= 0;
        data_ram_sys_if.a <= data_ram_sys_if.a + 1;
        data_ram_read[0] <= 1;
        if (pt_size == 1) cnt <= cnt + 1;
      end

    end
    3: begin
      get_next_inst();
    end
  endcase
endtask

// Use this FIFO - width converter to send out interrupt messages
axis_dwidth_converter_48_to_8 interrupt_converter_48_to_8 (
  .aclk   ( i_clk  ),                    // input wire aclk
  .aresetn( ~i_rst ),              // input wire aresetn
  .s_axis_tvalid( interrupt_in_if.val  ),  // input wire s_axis_tvalid
  .s_axis_tready( interrupt_in_if.rdy  ),  // output wire s_axis_tready
  .s_axis_tdata ( interrupt_in_if.dat  ),    // input wire [383 : 0] s_axis_tdata
  .s_axis_tlast ( interrupt_in_if.eop  ),    // input wire s_axis_tlast
  .m_axis_tvalid( interrupt_out_if.val ),  // output wire m_axis_tvalid
  .m_axis_tready( interrupt_out_if.rdy ),  // input wire m_axis_tready
  .m_axis_tdata ( interrupt_out_if.dat ),    // output wire [63 : 0] m_axis_tdata
  .m_axis_tlast ( interrupt_out_if.eop )    // output wire m_axis_tlast
);

// This just stores the index + length of interrupt packet
axi_stream_fifo #(
  .SIZE     ( 4  ),
  .DAT_BITS ( 16 + 3 )
)
interrupt_index_fifo (
  .i_clk ( i_clk ),
  .i_rst ( i_rst ),
  .i_axi ( idx_in_if ),
  .o_axi ( idx_out_if ),
  .o_full(),
  .o_emp ()
);

// Process for reading from FIFO and sending interrupt
always_comb begin
  interrupt_out_if.rdy = (interrupt_state == SEND_DATA) && (~tx_if.val || (tx_if.val && tx_if.rdy));
end

always_ff @ (posedge i_clk) begin
  if (i_rst) begin
    interrupt_rpl <= 0;
    interrupt_state <= WAIT_FIFO;
    interrupt_hdr_byt <= 0;
    idx_out_if.rdy <= 0;
    tx_if.reset_source();
  end else begin
    case (interrupt_state)
      WAIT_FIFO: begin
        idx_out_if.rdy <= 1;
        if (idx_out_if.val) begin
          idx_out_if.rdy <= 0;
          interrupt_state <= SEND_HDR;
          interrupt_rpl <= bls12_381_interrupt_rpl(idx_out_if.dat[0 +: 16], (point_type_t)'(idx_out_if.dat[16 +: 3]));
          interrupt_hdr_byt <= $bits(bls12_381_interrupt_rpl_t)/8;
        end
      end
      // Header needs to be aligned to AXI_STREAM_BYTS
      SEND_HDR: begin
        if (~tx_if.val || (tx_if.val && tx_if.rdy)) begin
          tx_if.sop <= interrupt_hdr_byt == $bits(bls12_381_interrupt_rpl_t)/8;
          tx_if.val <= 1;
          tx_if.dat <= interrupt_rpl;
          interrupt_rpl <= interrupt_rpl >> AXI_STREAM_BYTS*8;
          interrupt_hdr_byt <= interrupt_hdr_byt - 8;
          if (interrupt_hdr_byt <= 8) interrupt_state <= SEND_DATA;
        end
      end
      SEND_DATA: begin
        if (~tx_if.val || (tx_if.val && tx_if.rdy)) begin
          tx_if.sop <= 0;
          tx_if.val <= interrupt_out_if.val;
          tx_if.dat <= interrupt_out_if.dat;
          tx_if.eop <= interrupt_out_if.eop;
          if (tx_if.eop) begin
            tx_if.reset_source();
            interrupt_state <= WAIT_FIFO;
          end
        end
      end
    endcase
  end
end
endmodule

         data_ram_sys_if.a <= curr_inst.a;
         data_ram_read[0] <= 1;
         cnt <= 5;


/**
 *    bsg_manycore_tile_vcache.v
 *
 *    A vcache tile that contains bsg_cache, vertical mesh router. horizontal wormhole router.
 *    and bsg_manycore_link_to cache adapter, and bsg_cache dma to wormhole adapter.
 *    this tile can connect to the top and bottom side of the compute tile array.
 *    the vcache DMA interface is connected to the horizontal 1D wormhole ruche network.
 */

`include "bsg_manycore_defines.svh"
`include "bsg_cache.svh"

module bsg_manycore_tile_vcache
  import bsg_noc_pkg::*;
  import bsg_cache_pkg::*;
  import bsg_manycore_pkg::*;
  #(`BSG_INV_PARAM(addr_width_p)
    , `BSG_INV_PARAM(data_width_p)
    , `BSG_INV_PARAM(x_cord_width_p)
    , `BSG_INV_PARAM(y_cord_width_p)

    , `BSG_INV_PARAM(num_tiles_y_p)

    , `BSG_INV_PARAM(vcache_addr_width_p)
    , `BSG_INV_PARAM(vcache_data_width_p)
    , `BSG_INV_PARAM(vcache_ways_p)
    , `BSG_INV_PARAM(vcache_sets_p)
    , `BSG_INV_PARAM(vcache_block_size_in_words_p)
    , `BSG_INV_PARAM(vcache_dma_data_width_p)
    , `BSG_INV_PARAM(vcache_word_tracking_p)

    , `BSG_INV_PARAM(icache_block_size_in_words_p)
    // wh_ruche_factor_p supported only for 2^n, n>0.
    , `BSG_INV_PARAM(wh_ruche_factor_p)
    , `BSG_INV_PARAM(wh_cid_width_p)
    , `BSG_INV_PARAM(wh_flit_width_p)
    , `BSG_INV_PARAM(wh_len_width_p)
    , `BSG_INV_PARAM(wh_cord_width_p)
    , localparam int wh_cord_markers_pos_lp[1:0] = '{wh_cord_width_p, 0}

    , parameter req_fifo_els_p=4

    , localparam lg_wh_ruche_factor_lp = `BSG_SAFE_CLOG2(wh_ruche_factor_p)

    , y_subcord_width_lp = `BSG_SAFE_CLOG2(num_tiles_y_p)

    , manycore_link_sif_width_lp =
      `bsg_manycore_link_sif_width(addr_width_p,data_width_p,x_cord_width_p,y_cord_width_p)

    , wh_link_sif_width_lp =
      `bsg_ready_and_link_sif_width(wh_flit_width_p)

    , parameter vcache_amo_support_p = (1 << e_cache_amo_swap)
                                | (1 << e_cache_amo_or)
                                | (1 << e_cache_amo_add)
  )
  (
    input clk_i
    , input reset_i
    , output logic reset_o

    , input  [wh_ruche_factor_p-1:0][E:W][wh_link_sif_width_lp-1:0] wh_link_sif_i
    , output [wh_ruche_factor_p-1:0][E:W][wh_link_sif_width_lp-1:0] wh_link_sif_o

    , input  [S:N][manycore_link_sif_width_lp-1:0] ver_link_sif_i
    , output [S:N][manycore_link_sif_width_lp-1:0] ver_link_sif_o

    // manycore cord
    , input [x_cord_width_p-1:0] global_x_i
    , input [y_cord_width_p-1:0] global_y_i

    , output logic [x_cord_width_p-1:0] global_x_o
    , output logic [y_cord_width_p-1:0] global_y_o
  );

  `declare_bsg_manycore_link_sif_s(addr_width_p,data_width_p,x_cord_width_p,y_cord_width_p);
  `declare_bsg_ready_and_link_sif_s(wh_flit_width_p, wh_link_sif_s);
  `declare_bsg_cache_pkt_s(vcache_addr_width_p,vcache_data_width_p);
  `declare_bsg_cache_dma_pkt_s(vcache_addr_width_p, vcache_block_size_in_words_p);

  // reset dff
  logic reset_r;
  bsg_dff #(
    .width_p(1)
  ) reset_dff (
    .clk_i(clk_i)
    ,.data_i(reset_i)
    ,.data_o(reset_r)
  );

  assign reset_o = reset_r;

  // feedthrough coordinate bits
  logic [x_cord_width_p-1:0] global_x_r;
  logic [y_cord_width_p-1:0] global_y_r;

  bsg_dff #(
    .width_p(x_cord_width_p)
  ) x_dff (
    .clk_i(clk_i)
    ,.data_i(global_x_i)
    ,.data_o(global_x_r)
  );

  bsg_dff #(
    .width_p(y_cord_width_p)
  ) y_dff (
    .clk_i(clk_i)
    ,.data_i(global_y_i)
    ,.data_o(global_y_r)
  );

  assign global_x_o = global_x_r;
  assign global_y_o = y_cord_width_p'(global_y_r+1);

  // mesh router
  // vcache connects to P
  bsg_manycore_link_sif_s [S:W] link_sif_li;
  bsg_manycore_link_sif_s [S:W] link_sif_lo;
  bsg_manycore_link_sif_s proc_link_sif_li;
  bsg_manycore_link_sif_s proc_link_sif_lo;

  bsg_manycore_mesh_node #(
    .x_cord_width_p(x_cord_width_p)
    ,.y_cord_width_p(y_cord_width_p)
    ,.data_width_p(data_width_p)
    ,.addr_width_p(addr_width_p)
    // Because vcaches do not initiate packets, and there are no clients on the same Row,
    // horizontal manycore links are unnecessary.
    ,.stub_p(4'b0011) // stub E and W
  ) rtr (
    .clk_i(clk_i)
    ,.reset_i(reset_r)

    ,.links_sif_i(link_sif_li)
    ,.links_sif_o(link_sif_lo)

    ,.proc_link_sif_i(proc_link_sif_li)
    ,.proc_link_sif_o(proc_link_sif_lo)

    ,.global_x_i(global_x_r)
    ,.global_y_i(global_y_r)
  );

  assign ver_link_sif_o[S] = link_sif_lo[S];
  assign link_sif_li[S] = ver_link_sif_i[S];
  assign ver_link_sif_o[N] = link_sif_lo[N];
  assign link_sif_li[N] = ver_link_sif_i[N];

  assign link_sif_li[E] = '0;
  assign link_sif_li[W] = '0;

  // link_to_cache
  bsg_cache_pkt_s cache_pkt;
  logic cache_v_li;
  logic cache_yumi_lo;
  logic [vcache_data_width_p-1:0] cache_data_lo;
  logic cache_v_lo;
  logic cache_yumi_li;

  logic wh_dest_east_not_west_lo;

  bsg_manycore_link_to_cache #(
    .link_addr_width_p(addr_width_p) // word addr
    ,.data_width_p(data_width_p)
    ,.x_cord_width_p(x_cord_width_p)
    ,.y_cord_width_p(y_cord_width_p)

    ,.icache_block_size_in_words_p(icache_block_size_in_words_p)
    ,.sets_p(vcache_sets_p)
    ,.ways_p(vcache_ways_p)
    ,.block_size_in_words_p(vcache_block_size_in_words_p)

    ,.fifo_els_p(req_fifo_els_p)
  ) link_to_cache (
    .clk_i(clk_i)
    ,.reset_i(reset_r)

    ,.link_sif_i(proc_link_sif_lo)
    ,.link_sif_o(proc_link_sif_li)

    ,.cache_pkt_o(cache_pkt)
    ,.v_o(cache_v_li)
    ,.yumi_i(cache_yumi_lo)

    ,.data_i(cache_data_lo)
    ,.v_i(cache_v_lo)
    ,.yumi_o(cache_yumi_li)

    ,.v_we_i(v_we_lo)

    ,.wh_dest_east_not_west_o(wh_dest_east_not_west_lo)
  );

  // vcache
  bsg_cache_dma_pkt_s dma_pkt_lo;
  logic dma_pkt_v_lo;
  logic dma_pkt_yumi_li;

  logic [vcache_dma_data_width_p-1:0] dma_data_li;
  logic dma_data_v_li;
  logic dma_data_ready_lo;

  logic [vcache_dma_data_width_p-1:0] dma_data_lo;
  logic dma_data_v_lo;
  logic dma_data_yumi_li;

  bsg_cache #(
    .addr_width_p(vcache_addr_width_p)
    ,.data_width_p(vcache_data_width_p)
    ,.block_size_in_words_p(vcache_block_size_in_words_p)
    ,.sets_p(vcache_sets_p)
    ,.ways_p(vcache_ways_p)
    ,.word_tracking_p(vcache_word_tracking_p)
    ,.dma_data_width_p(vcache_dma_data_width_p)
    ,.amo_support_p(vcache_amo_support_p)
  ) cache (
    .clk_i(clk_i)
    ,.reset_i(reset_r)

    // to manycore
    ,.cache_pkt_i(cache_pkt)
    ,.v_i(cache_v_li)
    ,.yumi_o(cache_yumi_lo)

    ,.data_o(cache_data_lo)
    ,.v_o(cache_v_lo)
    ,.yumi_i(cache_yumi_li)

    ,.v_we_o(v_we_lo)

    // to wormhole
    ,.dma_pkt_o(dma_pkt_lo)
    ,.dma_pkt_v_o(dma_pkt_v_lo)
    ,.dma_pkt_yumi_i(dma_pkt_yumi_li)

    ,.dma_data_i(dma_data_li)
    ,.dma_data_v_i(dma_data_v_li)
    ,.dma_data_ready_and_o(dma_data_ready_lo)

    ,.dma_data_o(dma_data_lo)
    ,.dma_data_v_o(dma_data_v_lo)
    ,.dma_data_yumi_i(dma_data_yumi_li)
  );

  // cache DMA to wormhole
  wh_link_sif_s cache_wh_link_li;
  wh_link_sif_s cache_wh_link_lo;

  bsg_cache_dma_to_wormhole #(
    .dma_addr_width_p(vcache_addr_width_p)
    ,.dma_mask_width_p(vcache_block_size_in_words_p)
    ,.dma_burst_len_p(vcache_block_size_in_words_p*vcache_data_width_p/vcache_dma_data_width_p)

    ,.wh_flit_width_p(wh_flit_width_p)
    ,.wh_cid_width_p(wh_cid_width_p)
    ,.wh_len_width_p(wh_len_width_p)
    ,.wh_cord_width_p(wh_cord_width_p)
  ) dma_to_wh (
    .clk_i(clk_i)
    ,.reset_i(reset_r)

    ,.dma_pkt_i(dma_pkt_lo)
    ,.dma_pkt_v_i(dma_pkt_v_lo)
    ,.dma_pkt_yumi_o(dma_pkt_yumi_li)

    ,.dma_data_o(dma_data_li)
    ,.dma_data_v_o(dma_data_v_li)
    ,.dma_data_ready_and_i(dma_data_ready_lo)

    ,.dma_data_i(dma_data_lo)
    ,.dma_data_v_i(dma_data_v_lo)
    ,.dma_data_yumi_o(dma_data_yumi_li)

    ,.wh_link_sif_i(cache_wh_link_li)
    ,.wh_link_sif_o(cache_wh_link_lo)

    ,.my_wh_cord_i(global_x_r)
    ,.dest_wh_cord_i({wh_cord_width_p{wh_dest_east_not_west_lo}})
    ,.dest_wh_cid_i('0)
    // concentrator id
    // lower bits come from lower bits of global_x
    // upper bits come from whether its north or south vc.
    ,.my_wh_cid_i({~global_y_r[y_subcord_width_lp-1], {wh_ruche_factor_p>1{global_x_r[0+:lg_wh_ruche_factor_lp]}}})
  );

  // wormhole router
  // vcache DMA connects to P
  wh_link_sif_s [E:P] wh_link_li;
  wh_link_sif_s [E:P] wh_link_lo;

  bsg_wormhole_router #(
    .flit_width_p(wh_flit_width_p)
    ,.dims_p(1)
    ,.cord_markers_pos_p(wh_cord_markers_pos_lp)
    ,.len_width_p(wh_len_width_p)
  ) wh_rtr (
    .clk_i(clk_i)
    ,.reset_i(reset_r)

    ,.link_i(wh_link_li)
    ,.link_o(wh_link_lo)

    ,.my_cord_i(global_x_r)
  );

  assign wh_link_li[P] = cache_wh_link_lo;
  assign cache_wh_link_li = wh_link_lo[P];

  // connect wh ruche links
  assign wh_link_sif_o[0][E] = wh_link_lo[E];
  assign wh_link_li[E] = wh_link_sif_i[0][E];
  assign wh_link_sif_o[0][W] = wh_link_lo[W];
  assign wh_link_li[W] = wh_link_sif_i[0][W];

  // feedthrough ruche links
  for (genvar i = 1; i < wh_ruche_factor_p; i++) begin
    assign wh_link_sif_o[i][E] = wh_link_sif_i[i][W];
    assign wh_link_sif_o[i][W] = wh_link_sif_i[i][E];
  end

endmodule

`BSG_ABSTRACT_MODULE(bsg_manycore_tile_vcache)


/**
 *    bsg_manycore_hor_io_router_column.v
 *
 *    This modules instantiates a vertical chain of bsg_manycore_hor_io_router,
 *    which can attach to the side of the pods to provide accelerator connectivity.
 */

`include "bsg_manycore_defines.svh"

module bsg_manycore_hor_io_router_column
  import bsg_noc_pkg::*;
  import bsg_manycore_pkg::*;
  #(`BSG_INV_PARAM(addr_width_p)
    , `BSG_INV_PARAM(data_width_p)
    , `BSG_INV_PARAM(x_cord_width_p)
    , `BSG_INV_PARAM(y_cord_width_p)
    , `BSG_INV_PARAM(ruche_factor_X_p)

    , `BSG_INV_PARAM(num_row_p)
    , `BSG_INV_PARAM(bit [num_row_p-1:0] tieoff_west_p)
    , `BSG_INV_PARAM(bit [num_row_p-1:0] tieoff_east_p)

    , localparam link_sif_width_lp =
      `bsg_manycore_link_sif_width(addr_width_p,data_width_p,x_cord_width_p,y_cord_width_p)
    , localparam ruche_x_link_sif_width_lp =
      `bsg_manycore_ruche_x_link_sif_width(addr_width_p,data_width_p,x_cord_width_p,y_cord_width_p)
  )
  (
    input clk_i
    , input reset_i

    // vertical links
    , input  [S:N][link_sif_width_lp-1:0] ver_link_sif_i
    , output [S:N][link_sif_width_lp-1:0] ver_link_sif_o

    , input  [num_row_p-1:0][link_sif_width_lp-1:0] proc_link_sif_i
    , output [num_row_p-1:0][link_sif_width_lp-1:0] proc_link_sif_o

    , input  [num_row_p-1:0][E:W][link_sif_width_lp-1:0] hor_link_sif_i
    , output [num_row_p-1:0][E:W][link_sif_width_lp-1:0] hor_link_sif_o

    , input  [num_row_p-1:0][E:W][ruche_x_link_sif_width_lp-1:0] ruche_link_i
    , output [num_row_p-1:0][E:W][ruche_x_link_sif_width_lp-1:0] ruche_link_o

    , input [x_cord_width_p-1:0] global_x_i
    , input [num_row_p-1:0][y_cord_width_p-1:0] global_y_i
  );

  `declare_bsg_manycore_link_sif_s(addr_width_p,data_width_p,x_cord_width_p,y_cord_width_p);
  bsg_manycore_link_sif_s [num_row_p-1:0][S:W] link_sif_li;
  bsg_manycore_link_sif_s [num_row_p-1:0][S:W] link_sif_lo;

  for (genvar i = 0; i < num_row_p; i++) begin: r

    bsg_manycore_hor_io_router #(
      .addr_width_p(addr_width_p)
      ,.data_width_p(data_width_p)
      ,.x_cord_width_p(x_cord_width_p)
      ,.y_cord_width_p(y_cord_width_p)
      ,.ruche_factor_X_p(ruche_factor_X_p)

      ,.tieoff_west_p(tieoff_west_p[i])
      ,.tieoff_east_p(tieoff_east_p[i])
    ) io_rtr (
      .clk_i(clk_i)
      ,.reset_i(reset_i)

      ,.link_sif_i(link_sif_li[i])
      ,.link_sif_o(link_sif_lo[i])

      ,.proc_link_sif_i(proc_link_sif_i[i])
      ,.proc_link_sif_o(proc_link_sif_o[i])

      ,.ruche_link_i(ruche_link_i[i])
      ,.ruche_link_o(ruche_link_o[i])

      ,.global_x_i(global_x_i)
      ,.global_y_i(global_y_i[i])
    );

    assign link_sif_li[i][W] = hor_link_sif_i[i][W];
    assign hor_link_sif_o[i][E] = link_sif_lo[i][E];
    assign link_sif_li[i][E] = hor_link_sif_i[i][E];

    if (i != num_row_p-1) begin
      assign link_sif_li[i][S] = link_sif_lo[i+1][N];
      assign link_sif_li[i+1][N] = link_sif_lo[i][S];
    end

  end

  assign ver_link_sif_o[N] = link_sif_lo[0][N];
  assign link_sif_li[0][N] = ver_link_sif_i[N];

  assign ver_link_sif_o[S] = link_sif_lo[num_row_p-1][S];
  assign link_sif_li[num_row_p-1][S] = ver_link_sif_i[S];

endmodule

`BSG_ABSTRACT_MODULE(bsg_manycore_hor_io_router_column)

assign hor_link_sif_o[i][W] = link_sif_lo[i][W];


//========================================
//
// Copyright (c) 2022 PEZY Computing, K.K.
//
//========================================
module pzbcm_matrix_arbiter
  import  pzbcm_arbiter_pkg::*;
#(
  parameter int                           REQUESTS        = 2,
  parameter pzbcm_arbiter_priority_matrix PRIORITY_MATRIX = '1,
  parameter bit                           ONEHOT_GRANT    = 1,
  parameter int                           GRANT_WIDTH     = calc_grant_width(REQUESTS, ONEHOT_GRANT)
)(
  input   var                       i_clk,
  input   var                       i_rst_n,
  input   var                       i_enable,
  input   var pzbcm_arbiter_config  i_config,
  input   var [REQUESTS-1:0]        i_request,
  output  var [GRANT_WIDTH-1:0]     o_grant
);
  logic [REQUESTS-1:0]                request;
  logic [REQUESTS-1:0]                grant;
  logic [REQUESTS-1:0][REQUESTS-1:0]  priority_matrix;
  logic [REQUESTS-1:0][REQUESTS-1:0]  priority_matrix_next;

//--------------------------------------------------------------
//  Grant generation
//--------------------------------------------------------------
  if (ONEHOT_GRANT) begin : g_onehot_grant
    always_comb begin
      o_grant = grant;
    end
  end
  else begin : g_binary_grant
    pzbcm_onehot #(REQUESTS)  u_onehot();
    always_comb begin
      o_grant = u_onehot.to_binary(grant);
    end
  end

  always_comb begin
    request = (i_enable) ? i_request : '0;
    grant   = compute_grant(request, priority_matrix);
  end

  function automatic logic [REQUESTS-1:0] compute_grant(
    logic [REQUESTS-1:0]                request,
    logic [REQUESTS-1:0][REQUESTS-1:0]  priority_matrix
  );
    logic [REQUESTS-1:0]  grant;

    for (int columun = 0;columun < REQUESTS;++columun) begin
      logic [REQUESTS-1:0]  columun_vector;

      for (int row = 0;row < REQUESTS;++row) begin
        columun_vector[row] = (row != columun) && request[row] && priority_matrix[row][columun];
      end

      grant[columun]  = request[columun] && (columun_vector == '0);
    end

    return grant;
  endfunction

//--------------------------------------------------------------
//  Priority matrix
//--------------------------------------------------------------
  always_comb begin
    priority_matrix_next  = update_priority_matrix(i_config, priority_matrix, grant);
  end

  for (genvar row = 0;row < REQUESTS;++row) begin : g_row
    for (genvar columun = 0;columun < REQUESTS;++columun) begin : g_column
      if (columun < row) begin : g
        always_comb begin
          priority_matrix[row][columun] = ~priority_matrix[columun][row];
        end
      end
      else if (columun == row) begin : g
        always_comb begin
          priority_matrix[row][columun] = '0;
        end
      end
      else begin : g
        logic priority_value;

        always_ff @(posedge i_clk, negedge i_rst_n) begin
          if (!i_rst_n) begin
            priority_value  <= PRIORITY_MATRIX[row][columun];
          end
          else if (i_config.reset) begin
            priority_value  <= i_config.priority_matrix[row][columun];
          end
          else if (request != '0) begin
            priority_value  <= priority_matrix_next[row][columun];
          end
        end

        always_comb begin
          priority_matrix[row][columun] = priority_value;
        end
      end
    end
  end

  function automatic logic [REQUESTS-1:0][REQUESTS-1:0] update_priority_matrix(
    pzbcm_arbiter_config                arbiter_config,
    logic [REQUESTS-1:0][REQUESTS-1:0]  priority_matrix,
    logic [REQUESTS-1:0]                grant
  );
    logic [REQUESTS-1:0]                update_position;
    logic                               row_value;
    logic                               columun_value;
    logic [REQUESTS-1:0][REQUESTS-1:0]  matrix_next;

    case (arbiter_config.arbiter_type)
      PZBCM_ARBITER_ROUND_ROBIN,
      PZBCM_ARBITER_INCREMENTAL_ROUND_ROBIN: begin
        for (int row = 0;row < REQUESTS;++row) begin
          update_position[row]  = is_highest_priority(row, priority_matrix[row]);
        end
        row_value     = '0;
        columun_value = '1;
      end
      PZBCM_ARBITER_DECREMENTAL_ROUND_ROBIN: begin
        for (int row = 0;row < REQUESTS;++row) begin
          update_position[row]  = is_lowest_priority(priority_matrix[row]);
        end
        row_value     = '1;
        columun_value = '0;
      end
      PZBCM_ARBITER_LRU: begin
        update_position = grant;
        row_value       = '0;
        columun_value   = '1;
      end
      PZBCM_ARBITER_MRU: begin
        update_position = grant;
        row_value       = '1;
        columun_value   = '0;
      end
      default: begin
        update_position = '0;
        row_value       = '0;
        columun_value   = '0;
      end
    endcase

    matrix_next = priority_matrix;
    for (int row = 0;row < REQUESTS;++row) begin
      for (int columun = row + 1;columun < REQUESTS;++columun) begin
        if (update_position[columun]) begin
          matrix_next[row][columun] = columun_value;
        end
        else if (update_position[row]) begin
          matrix_next[row][columun] = row_value;
        end
      end
    end

    return matrix_next;
  endfunction

  function automatic logic is_highest_priority(int row_index, logic [REQUESTS-1:0] row_vector);
    logic [REQUESTS-1:0]  vector;
    vector            = row_vector;
    vector[row_index] = '1;
    return vector == '1;
  endfunction

  function automatic logic is_lowest_priority(logic [REQUESTS-1:0] row_vector);
    return row_vector == '0;
  endfunction

`ifndef SYNTHESIS
  bit [REQUESTS-1:0][$clog2(REQUESTS)-1:0]  priority_values;

  always_comb begin
    for (int i = 0;i < REQUESTS;++i) begin
      priority_values[i]  = $countones(priority_matrix[i]);
    end
  end

  `ifdef PZBCM_ARBITER_ENABLE_SVA
  tb_pzbcm_matrix_arbiter_sva #(REQUESTS) u_sva (
    .i_clk              (i_clk            ),
    .i_rst_n            (i_rst_n          ),

    .i_busy             (busy             ),
    .i_request          (i_request        ),
    .i_grant            (grant[1]         ),
    .i_priority_matrix  (priority_matrix  )
  );


//========================================
//
// Copyright (c) 2022 PEZY Computing, K.K.
//
//========================================
module pzcorebus_request_m_to_1_switch
  import  pzcorebus_pkg::*,
          pzbcm_arbiter_pkg::*,
          pzbcm_selector_pkg::*;
#(
  parameter pzcorebus_config          BUS_CONFIG        = '0,
  parameter int                       SLAVES            = 2,
  parameter bit [1:0]                 ENABLE_ARBITER    = 2'b01,
  parameter int                       PRIORITY_WIDTH    = 0,
  parameter int                       WEIGHT_WIDTH      = 0,
  parameter pzbcm_arbiter_weight_list WEIGHT            = '1,
  parameter bit                       WAIT_FOR_DATA     = 0,
  parameter bit                       WAIT_FOR_RESPONSE = 0,
  parameter bit                       SLAVE_FIFO        = '0,
  parameter bit                       MASTER_FIFO       = '0,
  parameter int                       COMMAND_DEPTH     = 2,
  parameter int                       DATA_DEPTH        = 2,
  parameter bit                       ALIGN_OUT         = 0,
  parameter bit                       SVA_CHECKER       = 1
)(
  input   var                       i_clk,
  input   var                       i_rst_n,
  input   var pzbcm_arbiter_config  i_arbiter_config,
  output  var [SLAVES-1:0]          o_response_select,
  input   var                       i_response_ack,
  interface.request_slave           slave_if[SLAVES],
  interface.request_master          master_if
);
  pzcorebus_request_if #(BUS_CONFIG)  aligner_if[SLAVES]();
  pzcorebus_request_if #(BUS_CONFIG)  switch_if[SLAVES+1]();

//--------------------------------------------------------------
//  Slave Command/Data Aligner
//--------------------------------------------------------------
  for (genvar i = 0;i < SLAVES;++i) begin : g_slave_aligner
    if (is_memory_profile(BUS_CONFIG)) begin : g
      pzcorebus_command_data_aligner_core #(
        .BUS_CONFIG               (BUS_CONFIG     ),
        .WAIT_FOR_DATA            (WAIT_FOR_DATA  ),
        .RELAX_MODE               (1              ),
        .THROUGH_NO_DATA_COMMAND  (1              ),
        .SLAVE_FIFO               (SLAVE_FIFO     ),
        .COMMAND_DEPTH            (COMMAND_DEPTH  ),
        .DATA_DEPTH               (DATA_DEPTH     ),
        .SVA_CHECKER              (SVA_CHECKER    )
      ) u_aligner (
        .i_clk        (i_clk          ),
        .i_rst_n      (i_rst_n        ),
        .o_mcmd_done  (),
        .o_mdata_done (),
        .o_mcmd       (),
        .o_mid        (),
        .o_maddr      (),
        .o_minfo      (),
        .slave_if     (slave_if[i]    ),
        .master_if    (aligner_if[i]  )
      );
    end
    else begin : g
      pzcorebus_request_fifo #(
        .BUS_CONFIG     (BUS_CONFIG     ),
        .COMMAND_DEPTH  (COMMAND_DEPTH  ),
        .COMMAND_VALID  (SLAVE_FIFO     ),
        .DATA_DEPTH     (DATA_DEPTH     ),
        .DATA_VALID     (SLAVE_FIFO     ),
        .SVA_CHECKER    (SVA_CHECKER    )
      ) u_slave_fifo (
        .i_clk          (i_clk          ),
        .i_rst_n        (i_rst_n        ),
        .i_clear        (1'b0           ),
        .o_empty        (),
        .o_almost_full  (),
        .o_full         (),
        .slave_if       (slave_if[i]    ),
        .master_if      (aligner_if[i]  )
      );
    end
  end

//--------------------------------------------------------------
//  Switch
//--------------------------------------------------------------
  logic [SLAVES-1:0]  command_valid;
  logic [SLAVES-1:0]  command_grand;
  logic [SLAVES-1:0]  command_ack;
  logic               command_done;
  logic [SLAVES-1:0]  data_grant;
  logic               response_idle;

  for (genvar i = 0;i < SLAVES;++i) begin : g_request
    logic command_ready;

    always_comb begin
      command_ready =
        response_idle &&
        ((!command_done) || switch_if[i].is_command_no_data());

      aligner_if[i].scmd_accept = command_ready && switch_if[i].scmd_accept;
      switch_if[i].mcmd_valid   = command_ready && aligner_if[i].mcmd_valid;
      switch_if[i].put_command(aligner_if[i].get_command());
    end

    always_comb begin
      aligner_if[i].sdata_accept  = switch_if[i].sdata_accept;
      switch_if[i].mdata_valid    = aligner_if[i].mdata_valid;
      switch_if[i].put_write_data(aligner_if[i].get_write_data());
    end

    always_comb begin
      command_valid[i]  = switch_if[i].mcmd_valid;
      command_ack[i]    = switch_if[i].command_ack();
    end
  end

  if (is_memory_profile(BUS_CONFIG)) begin : g_data_grant
    logic [2:0]         done;
    logic               data_done;
    logic [SLAVES-1:0]  grant_latched;

    always_comb begin
      done[0] = switch_if[SLAVES].command_with_data_ack() && switch_if[SLAVES].write_data_last_ack();

      if (!i_rst_n) begin
        command_done  <= '0;
        data_done     <= '0;
      end
      else if (done != '0) begin
        command_done  <= '0;
        data_done     <= '0;
      end
      else if (switch_if[SLAVES].command_with_data_ack()) begin
        command_done  <= '1;
        data_done     <= '0;
      end
      else if (switch_if[SLAVES].write_data_last_ack()) begin
        command_done  <= '0;
        data_done     <= '1;
      end
    end

    always_comb begin
      if (command_done) begin
        data_grant  = grant_latched;
      end
      else begin
        data_grant  = command_grand;
      end
    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
      if (!i_rst_n) begin
        grant_latched <= '0;
      end
      else if (switch_if[SLAVES].command_with_data_ack()) begin
        grant_latched <= command_grand;
      end
    end
  end
  else begin : g_data_grant
    always_comb begin
      command_done  = '0;
      data_grant    = '0;
    end
  end

  pzbcm_arbiter #(
    .REQUESTS       (SLAVES         ),
    .ENABLE_ARBITER (ENABLE_ARBITER ),
    .PRIORITY_WIDTH (PRIORITY_WIDTH ),
    .WEIGHT_WIDTH   (WEIGHT_WIDTH   ),
    .WEIGHT         (WEIGHT         ),
    .ONEHOT_GRANT   (1              ),
    .KEEP_RESULT    (1              )
  ) u_arbiter (
    .i_clk      (i_clk            ),
    .i_rst_n    (i_rst_n          ),
    .i_config   (i_arbiter_config ),
    .i_request  (command_valid    ),
    .o_grant    (command_grand    ),
    .i_free     (command_ack      )
  );

  pzcorebus_request_mux #(
    .BUS_CONFIG     (BUS_CONFIG             ),
    .SLAVES         (SLAVES                 ),
    .SELECTOR_TYPE  (PZBCM_SELECTOR_ONEHOT  )
  ) u_mux (
    .i_command_select     (command_grand          ),
    .i_write_data_select  (data_grant             ),
    .slave_if             (switch_if[0:SLAVES-1]  ),
    .master_if            (switch_if[SLAVES]      )
  );

  if (WAIT_FOR_RESPONSE) begin : g_response_select
    logic [SLAVES-1:0]  response_select;

    always_comb begin
      o_response_select = response_select;
    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
      if (!i_rst_n) begin
        response_idle <= '1;
      end
      else if (i_response_ack) begin
        response_idle <= '1;
      end
      else if (switch_if[SLAVES].command_non_posted_ack()) begin
        response_idle <= '0;
      end
    end

    always_ff @(posedge i_clk, negedge i_rst_n) begin
      if (!i_rst_n) begin
        response_select <= '0;
      end
      else if (switch_if[SLAVES].command_non_posted_ack()) begin
        response_select <= command_grand;
      end
    end
  end
  else begin : g_response_select
    always_comb begin
      o_response_select = '0;
    end

    always_comb begin
      response_idle = '1;
    end
  end

//--------------------------------------------------------------
//  Master Command/Data Alignment
//--------------------------------------------------------------
  if (is_memory_profile(BUS_CONFIG) && ALIGN_OUT && MASTER_FIFO) begin : g_master_aligner
    pzcorebus_command_data_aligner #(
      .BUS_CONFIG     (BUS_CONFIG     ),
      .RELAX_MODE     (1              ),
      .SLAVE_FIFO     (1              ),
      .COMMAND_DEPTH  (COMMAND_DEPTH  ),
      .DATA_DEPTH     (DATA_DEPTH     ),
      .SVA_CHECKER    (0              )
    ) u_aligner (
      .i_clk      (i_clk              ),
      .i_rst_n    (i_rst_n            ),
      .slave_if   (switch_if[SLAVES]  ),
      .master_if  (master_if          )
    );
  end
  else begin : g_master_aligner
    pzcorebus_request_fifo #(
      .BUS_CONFIG     (BUS_CONFIG     ),
      .COMMAND_DEPTH  (COMMAND_DEPTH  ),
      .COMMAND_VALID  (MASTER_FIFO    ),
      .DATA_DEPTH     (DATA_DEPTH     ),
      .DATA_VALID     (MASTER_FIFO    ),
      .SVA_CHECKER    (0              )
    ) u_fifo (
      .i_clk          (i_clk              ),
      .i_rst_n        (i_rst_n            ),
      .i_clear        ('0                 ),
      .o_empty        (),
      .o_almost_full  (),
      .o_full         (),
      .slave_if       (switch_if[SLAVES]  ),
      .master_if      (master_if          )
    );
  end
endmodule

      done[1] = switch_if[SLAVES].command_with_data_ack() && data_done;
      done[2] = switch_if[SLAVES].write_data_last_ack() && command_done;
    end


`include "cpu_defs.svh"

module tlb(
	input  logic        clk,
	input  logic        rst,
	input  logic [7:0]  asid,
	input  virt_t       inst_vaddr,
	input  virt_t       [`ISSUE_NUM-1:0] data_vaddr,
	output tlb_result_t inst_result,
	output tlb_result_t [`ISSUE_NUM-1:0] data_result,

	// for TLBR/TLBWI/TLBWR
	input  tlb_index_t  tlbrw_index,
	input  logic        tlbrw_we,
	input  tlb_entry_t  tlbrw_wdata,
	output tlb_entry_t  tlbrw_rdata,

	// for TLBP
	input  uint32_t     tlbp_entry_hi,
	output uint32_t     tlbp_index
);

tlb_entry_t [`TLB_ENTRIES_NUM-1:0] entries;
assign tlbrw_rdata = entries[tlbrw_index];

genvar i;

			end
		end
	end
endgenerate

tlb_lookup inst_lookup(
	.entries,
	.virt_addr(inst_vaddr),
	.asid,
	.result(inst_result)
);

for(genvar i = 0; i < `ISSUE_NUM; ++i) begin : gen_data_lookup
	tlb_lookup data_lookup(
		.entries,
		.virt_addr(data_vaddr[i]),
		.asid,
		.result(data_result[i])
	);
end

tlb_result_t tlbp_result;
tlb_lookup tlbp_lookup(
	.entries,
	.virt_addr(tlbp_entry_hi),
	.asid(tlbp_entry_hi[7:0]),
	.result(tlbp_result)
);

assign tlbp_index = {
	tlbp_result.miss,
	{(32 - $clog2(`TLB_ENTRIES_NUM) - 1){1'b0}},
	tlbp_result.which
};

endmodule

generate
	for(i = 0; i < `TLB_ENTRIES_NUM; ++i)
	begin: gen_for_tlb
		always_ff @(posedge clk) begin
			if(rst) begin


//-----------------------------------------------------------------
//                     USB Full Speed Host
//                           V0.5
//                     Ultra-Embedded.com
//                     Copyright 2015-2019
//
//                 Email: admin@ultra-embedded.com
//
//                         License: GPL
// If you would like a version with a more permissive license for
// use in closed source commercial applications please contact me
// for details.
//-----------------------------------------------------------------
//
// This file is open source HDL; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// This file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this file; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA
//-----------------------------------------------------------------

//-----------------------------------------------------------------
//                          Generated File
//-----------------------------------------------------------------
//-----------------------------------------------------------------
// Module: 5-bit CRC used by USB tokens
//-----------------------------------------------------------------
module usbh_crc5
(
    input [4:0]     crc_i,
    input [10:0]    data_i,
    output [4:0]    crc_o
);

//-----------------------------------------------------------------
// Implementation
//-----------------------------------------------------------------
assign crc_o[0] =    data_i[10] ^ data_i[9] ^ data_i[6] ^ data_i[5] ^ data_i[3] ^ data_i[0] ^
                       crc_i[0] ^ crc_i[3] ^ crc_i[4];

assign crc_o[1] =    data_i[10] ^ data_i[7] ^ data_i[6] ^ data_i[4] ^ data_i[1] ^
                       crc_i[0] ^ crc_i[1] ^ crc_i[4];

assign crc_o[2] =    data_i[10] ^ data_i[9] ^ data_i[8] ^ data_i[7] ^ data_i[6] ^ data_i[3] ^ data_i[2] ^ data_i[0] ^
                       crc_i[0] ^ crc_i[1] ^ crc_i[2] ^ crc_i[3] ^ crc_i[4];

                       crc_i[2] ^ crc_i[3] ^ crc_i[4];

endmodule

assign crc_o[3] =    data_i[10] ^ data_i[9] ^ data_i[8] ^ data_i[7] ^ data_i[4] ^ data_i[3] ^ data_i[1] ^
                       crc_i[1] ^ crc_i[2] ^ crc_i[3] ^ crc_i[4];


// Greg Stitt
// Wes Piard
// University of Florida

// A simplified subset of the AXI4 streaming interface. I've included all the
// signal in the normal interface, but commented out the optional ones we won't
// be supporting in this example.

`ifndef _AXI4_STREAM_IF_
`define _AXI4_STREAM_IF_

interface axi4_stream_if #(
    parameter int DATA_WIDTH = 32
    /*parameter int ID_WIDTH = 4,
    parameter int DEST_WIDTH = 4,
    parameter int USER_WIDTH = 1*/
) (
    input logic aclk,
    input logic aresetn
);
    logic tvalid;
    logic tready;
    logic [DATA_WIDTH-1:0] tdata;
/*    logic [DATA_WIDTH/8-1:0] tstrb;
    logic [DATA_WIDTH/8-1:0] tkeep;
    logic tlast;
    logic [ID_WIDTH-1:0] tid;
    logic [DEST_WIDTH-1:0] tdest;
    logic [USER_WIDTH-1:0] tuser;*/

    // AXI requires byte aligned data widths, so confirm compliance here.
    initial begin

    end

    // If using the interface for synthesis, this will probably cause errors. We
    // use it here so we can use `uvm_error in the interface assertion.
    `include "uvm_macros.svh"
    import uvm_pkg::*;

    // Validate required properties of AXI: once tvalid is asserted, it must remain asserted until
    // tready is asserted.
    assert property (@(posedge aclk) disable iff (!aresetn) $fell(tvalid) |-> $past(tready, 1))
    else `uvm_error("ASSERT", "tvalid must be asserted continuously until tready is asserted.");

endinterface

`endif

if (DATA_WIDTH % 8 != 0) $fatal(1, $sformatf("AXI DATA_WIDTH=%0d is not byte aligned", DATA_WIDTH));


// Greg Stitt
// University of Florida

// The environment is very similar to previous examples and has only been
// updated for the new DUT and the different number of interfaces. It also
// provides a function to configure the transaction abstraction level in each
// agent.

`ifndef _ACCUM_ENV_SVH_
`define _ACCUM_ENV_SVH_

`include "uvm_macros.svh"
import uvm_pkg::*;

class accum_env extends uvm_env;
    `uvm_component_utils(accum_env)

    axi4_stream_agent #(accum_tb_pkg::INPUT_WIDTH) agent_in;
    axi4_stream_agent #(accum_tb_pkg::OUTPUT_WIDTH) agent_out;

    accum_scoreboard scoreboard;

    accum_input_coverage input_coverage;
    accum_output_coverage output_coverage;

    virtual axi4_stream_if #(accum_tb_pkg::INPUT_WIDTH) in_vif;
    virtual axi4_stream_if #(accum_tb_pkg::OUTPUT_WIDTH) out_vif;

    // Configuration information for the drivers.
    int min_driver_delay;
    int max_driver_delay;

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

    function automatic void configure_transaction_level(bit is_packet_level);
        agent_in.configure_transaction_level(is_packet_level);
        agent_out.configure_transaction_level(is_packet_level);
        scoreboard.is_packet_level = is_packet_level;
    endfunction

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

        agent_out = axi4_stream_agent#(accum_tb_pkg::OUTPUT_WIDTH)::type_id::create("agent_out", this);
        scoreboard = accum_scoreboard::type_id::create("scoreboard", this);

        input_coverage = accum_input_coverage::type_id::create("input_coverage", this);
        output_coverage = accum_output_coverage::type_id::create("output_coverage", this);

        if (!uvm_config_db#(virtual axi4_stream_if #(accum_tb_pkg::INPUT_WIDTH))::get(this, "", "in_vif", in_vif)) `uvm_fatal("NO_VIF", {"Virtual interface must be set for: ", get_full_name()});
        if (!uvm_config_db#(virtual axi4_stream_if #(accum_tb_pkg::OUTPUT_WIDTH))::get(this, "", "out_vif", out_vif)) `uvm_fatal("NO_VIF", {"Virtual interface must be set for: ", get_full_name()});

        // Read the driver configuration information.
        if (!uvm_config_db#(int)::get(this, "", "min_driver_delay", min_driver_delay)) min_driver_delay = 1;
        if (!uvm_config_db#(int)::get(this, "", "max_driver_delay", max_driver_delay)) max_driver_delay = 1;
    endfunction

    function void connect_phase(uvm_phase phase);
        // Connect the virtual interfaces to each agent's driver and monitor.
        agent_in.driver.vif  = in_vif;
        agent_in.monitor.vif = in_vif;
        agent_out.driver.vif  = out_vif;
        agent_out.monitor.vif = out_vif;

        // Connect the analysis ports (ap) and exports (ae). Note that the
        // scoreboard internally uses analysis FIFOs, so we don't need to
        // setup the FIFOs here like we did in the previous example.
        agent_in.monitor.ap.connect(scoreboard.in_ae);
        agent_out.monitor.ap.connect(scoreboard.out_ae);

        // Configure the driver.
        agent_in.driver.set_delay(min_driver_delay, max_driver_delay);

        // Connect the coverage classes. Note that any analysis port can be
        // sent to any consumer.
        agent_in.monitor.ap.connect(input_coverage.in_ae);
        agent_out.monitor.ap.connect(output_coverage.out_ae);
    endfunction

endclass

`endif

agent_in = axi4_stream_agent#(accum_tb_pkg::INPUT_WIDTH)::type_id::create("agent_in", this);


/* Copyright 2018 Tymoteusz Blazejczyk
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "logic.svh"

/* Module: logic_axi4_lite_bus_main
 *
 * Multi-master multi-slave bus.
 *
 * Parameters:
 *  SLAVES          - Number of slaves connected to the AXI4-Lite bus.
 *  MASTERS         - Number of masters connected to the AXI4-Lite bus.
 *  DATA_BYTES      - Number of bytes for wdata and rdata signals.
 *  ADDRESS_WIDTH   - Number of bits for awaddr and araddr signals.
 *
 * Ports:
 *  aclk        - Clock.
 *  areset_n    - Asynchronous active-low reset.
 *  slave       - AXI4-Lite slave interface.
 *  master      - AXI$-Lite master interface.
 */
module logic_axi4_lite_bus_main #(
    int SLAVES = 1,
    int MASTERS = 1,
    int DATA_BYTES = 4,
    int ADDRESS_WIDTH = 1,
    logic_axi4_lite_bus_pkg::slave_t [SLAVES-1:0] MAP
) (
    input aclk,
    input areset_n,
    `LOGIC_MODPORT(logic_axi4_lite_if, slave) slave[MASTERS],
    `LOGIC_MODPORT(logic_axi4_lite_if, master) master[SLAVES]
);
    initial begin: design_rule_checks
        `LOGIC_DRC_EQUAL(MASTERS, 1)
        `LOGIC_DRC_EQUAL_OR_GREATER_THAN(SLAVES, 1)
        `LOGIC_DRC_EQUAL_OR_GREATER_THAN(ADDRESS_WIDTH, 1)
        `LOGIC_DRC_POWER_OF_2(DATA_BYTES)
        `LOGIC_DRC_RANGE(DATA_BYTES, 4, 8)
    end

    logic_axi4_lite_bus_multi_slave #(
        .SLAVES(SLAVES),
        .DATA_BYTES(DATA_BYTES),
        .ADDRESS_WIDTH(ADDRESS_WIDTH),
        .MAP(MAP)
    )

        .slave(slave[0]),
        .master(master),
        .*
    );
endmodule


/* Copyright 2018 Tymoteusz Blazejczyk
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "svunit_defines.svh"

`ifdef OVL_ASSERT_ON
`include "std_ovl_defines.h"
`endif

module logic_basic_gray2binary_unit_test;
    import svunit_pkg::svunit_testcase;

        for (int k = (WIDTH - 1); k > 0; --k) begin
            gray2binary[k - 1] = gray[k - 1] ^ gray2binary[k];
        end
    endfunction

    logic aclk = '0;
    logic areset_n = '0;

    logic [WIDTH-1:0] i = '0;
    logic [WIDTH-1:0] o;

    initial forever #1 aclk = ~aclk;

    clocking cb @(posedge aclk);
        output i;
        input o;
    endclocking

    logic_basic_gray2binary #(
        .WIDTH(WIDTH)
    )
    dut (
        .*
    );

    function void build();
        svunit_ut = new (name);
    endfunction

    task setup();
        svunit_ut.setup();

        cb.i <= '0;
        areset_n = '0;
        @(cb);
        areset_n = '1;
        @(cb);
    endtask

    task teardown();
        svunit_ut.teardown();

        areset_n = '0;
    endtask

`ifdef OVL_ASSERT_ON
    logic [`OVL_FIRE_WIDTH-1:0] assert_unknown_output_fire;

    ovl_never_unknown_async #(
        .severity_level(`OVL_FATAL),
        .width(WIDTH),
        .property_type(`OVL_ASSERT),
        .msg("output signal o cannot be unknown")
    )
    assert_unknown_input (
        .reset(areset_n),
        .enable(1'b1),
        .test_expr(i),
        .fire(assert_unknown_output_fire)
    );
`endif

`SVUNIT_TESTS_BEGIN

`SVTEST(basic)
    bit [WIDTH-1:0] gray[] = new [64];
    bit [WIDTH-1:0] binary[] = new [64];

    foreach (gray[i]) begin
        gray[i] = $urandom;
        binary[i] = gray2binary(gray[i]);

        cb.i <= gray[i];
        @(cb);
        `FAIL_UNLESS_EQUAL(binary[i], cb.o)
    end
`SVTEST_END

`SVUNIT_TESTS_END

endmodule

    string name = "logic_basic_gray2binary_unit_test";
    svunit_testcase svunit_ut;

    parameter WIDTH = 16;


/*
 *                              .--------------. .----------------. .------------.
 *                             | .------------. | .--------------. | .----------. |
 *                             | | ____  ____ | | | ____    ____ | | |   ______ | |
 *                             | ||_   ||   _|| | ||_   \  /   _|| | | .' ___  || |
 *       ___  _ __   ___ _ __  | |  | |__| |  | | |  |   \/   |  | | |/ .'   \_|| |
 *      / _ \| '_ \ / _ \ '_ \ | |  |  __  |  | | |  | |\  /| |  | | || |       | |
 *       (_) | |_) |  __/ | | || | _| |  | |_ | | | _| |_\/_| |_ | | |\ `.___.'\| |
 *      \___/| .__/ \___|_| |_|| ||____||____|| | ||_____||_____|| | | `._____.'| |
 *           | |               | |            | | |              | | |          | |
 *           |_|               | '------------' | '--------------' | '----------' |
 *                              '--------------' '----------------' '------------'
 *
 *  openHMC - An Open Source Hybrid Memory Cube Controller
 *  (C) Copyright 2014 Computer Architecture Group - University of Heidelberg
 *  www.ziti.uni-heidelberg.de
 *  B6, 26
 *  68159 Mannheim
 *  Germany
 *
 *  Contact: openhmc@ziti.uni-heidelberg.de
 *  http://ra.ziti.uni-heidelberg.de/openhmc
 *
 *   This source file is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This source file is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with this source file.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *  Module name: rx_descrambler
 *
 * Scrambler Logic (HMC Spec version 1.0)
 * This module implements a parallel scrambler based on the
 * polynomial 1+ x^(-14) + x^(-15).
 *
 * Such Scrambler is typically shown as a 15 bit Linear Feedback Shift Register
 * (LFSR) with bits shifting from register 1 on the left to register 15 on the
 * right, with register 14 and 15 combining to shift into register 1.
 * The HMC Serializer outputs data[0] first from parallel tx data[n:0],
 * so if data[n:0] is to be bitwise scrambled with LFSR[n:0], we need the LFSR
 * to shift from n -> 0, the opposite direction from the typical illustration.
 * This implementation shifts data from LFSR[14] on the left to LFSR[0] on the
 * right, with LFSR[1] and [0] combining to shift into LFSR[14]. This way
 * LFSR[14:0] can bitwise scramble data[14:0] and be compatible with serializ-
 * ation that shifts out on the data[0] side.
 * Put otherwise: Polynomial 1+ x^(-14) + x^(-15) is equiv to
 * x^15 + x^1 + x^0
 *
 * This parallelized version calculates the next DWIDTH steps of values for
 * the LFSR.  These bits are used to scramble the parallel input, and to
 * choose the next value of lfsr (lfsr_steps[DWIDTH-1]).
 *
 * This is the descrambler.  It is self-seeding.  When lock is asserted it has
 * successfully found the correct value for the LFSR.  It is only implemented
 * for DWIDTH > 14.
 *
 * Since we know that scrambled zeros are being translated, we can calculate
 * what the seed will be in the next timestep.  In order to simplify the
 * calculation, we assume that the top bit is a one.  That has the happy side-
 * effect of letting us know that the seed didn't get stuck at all zeros.
 *
 * After the scrambler is locked, the input word may need to be aligned.  The
 * bit_slip input allows the scrambler to shift one bit with the serializer
 * to keep the scrambler in sync.
 */

`default_nettype none

module rx_descrambler #(
    parameter DWIDTH=16,
    parameter BITSLIP_SHIFT_RIGHT=1
)
(
    input wire              clk,
    input wire              res_n,
    input wire              bit_slip,
    input wire              can_lock,
    output reg              locked,
    input wire [DWIDTH-1:0] data_in,
    output reg [DWIDTH-1:0] data_out

);

reg  [14:0]       lfsr;                    // LINEAR FEEDBACK SHIFT REGISTER
wire [14:0]       lfsr_slipped;            // Temporary lfsr for bitslip
wire [14:0]       lfsr_steps [DWIDTH-1:0]; // LFSR values for serial time steps
wire [14:0]       calculated_seed;
wire [DWIDTH-1:0] data_out_tmp;

generate
    if(BITSLIP_SHIFT_RIGHT==1) begin
        assign lfsr_slipped = { (lfsr_steps[DWIDTH-1][1] ^ lfsr_steps[DWIDTH-1][0]) , lfsr_steps[DWIDTH-1][14:1] };
    end else begin
        assign lfsr_slipped = { lfsr_steps[DWIDTH-1][13:0], (lfsr_steps[DWIDTH-1][14] ^ lfsr_steps[DWIDTH-1][0])};
    end
endgenerate

`ifdef SIMULATION
    initial begin
       lfsr     <= 15'h0;
    end
`endif

// SEQUENTIAL PROCESS
`ifdef ASYNC_RES
always @(posedge clk or negedge res_n)  begin `else
always @(posedge clk)  begin `endif
    `ifdef RESET_ALL
        if(!res_n) begin
            data_out <= {DWIDTH {1'b0}};

        end else
    `endif
    begin
        data_out <= data_out_tmp;
        if (!locked) begin
            lfsr <= calculated_seed;
        end else begin
            if (bit_slip) begin
                lfsr <= lfsr_slipped;
            end else begin
                lfsr <= lfsr_steps[DWIDTH-1];
            end
        end
    end

    if(!res_n) begin
        locked   <= 1'b0;
    end else begin
        if (!locked) begin
            if (calculated_seed == lfsr_steps[DWIDTH-1]) begin
                locked <= 1'b1;
            end
        end
        if(!can_lock) begin
            locked <= 1'b0;
        end
    end
end                 // serial shift right with left input

// SCRAMBLE

genvar j;
generate
    localparam OFFSET = DWIDTH-15; // It breaks here if DWIDTH < 15
    assign calculated_seed[14] = 1'b1; // Guess the top bit is 1

    // data_in is the past state of the LFSR, so we can figure out
    // the current value using a loop.

    for(j = 0; j < 14; j = j + 1) begin : seed_calc
        assign calculated_seed[j] = data_in[j+OFFSET] ^ data_in[j+OFFSET+1];
    end

    assign data_out_tmp [0] = data_in[0] ^ lfsr[0]; // single bit scrambled
    assign lfsr_steps[0]    = { (lfsr[1] ^ lfsr[0]) , lfsr[14:1] }; // lfsr at next bit clock
    for(j = 1; j < DWIDTH; j = j + 1) begin : scrambler_gen
        assign data_out_tmp[j] = data_in[j] ^ lfsr_steps[j-1][0];
        assign lfsr_steps[j]   = { (lfsr_steps[j-1][1] ^ lfsr_steps[j-1][0]) , lfsr_steps[j-1][14:1] };
    end
endgenerate

endmodule

`default_nettype wire