[RISCV] Rename {pc} to {rg_FetchPC} to avoid confuse

// Copyright (c) 2017 Min Chen.  All Rights Reserved.
// Author: Min Chen

// ================================================================
// BSV library imports

import RegFile       :: *;    // For RISC-V GPRs
import ConfigReg     :: *;
import FIFOF         :: *;
import SpecialFIFOs  :: *;

// ================================================================
// BSV project imports

import ISA_Decls :: *;    // Instruction encodings

// ================================================================
// Memory interface for CPU
// ================================================================

import BRAMCore :: *;
import DReg     :: *;

// ----------------
// IMem responses: either and exception or an instruction

typedef Word IMem_Resp;

// ----------------
// DMem request ops and sizes

typedef enum {
   MEM_OP_LOAD,
   MEM_OP_STORE
} Mem_Op deriving(Eq, Bits, FShow);

// ----------------
// DMem requests

typedef struct {
   Mem_Op               mem_op;
   Addr                 addr;
   Word                 data;          // Only relevant if mem_op == MEM_OP_STORE
   Bit#(Bytes_per_Word) written;
} DMem_Req deriving(Bits);

// ----------------
// DMem responses: either an exception or data
// (data value is only relevant for LOADs, irrelevant for STOREs)

typedef Maybe#(Word) DMem_Resp;

// ----------------
// Memory interface reference design

module mkMemory#(Reg#(Bit#(64)) cycles)(Memory_IFC);
   BRAM_PORT#(Bit#(14), Word)             imem <- mkBRAMCore1Load(valueOf(TExp#(14)), False, (genC ? "mem.vmh" : "R:/mem.vmh"), False);
   BRAM_DUAL_PORT_BE#(Bit#(15), Word, 4)  dmem <- mkBRAMCore2BELoad(valueOf(TExp#(15)), False, (genC ? "mem.vmh.D" : "R:/mem.vmh.D"), False);
   Reg#(Bool)                             dmem_rd  <- mkDReg(False);
   Reg#(Bit#(2))                          rg_shift <- mkRegU;

   method Action imem_req(Addr addr);
      let phyAddr = addr - imemSt;
      imem.put(False, truncate(phyAddr >> 2), ?);
      //$display("[%7d] [IMEM] ReqAddr = 0x%08h", cycles, addr);
   endmethod

   method ActionValue#(IMem_Resp) imem_resp();
      let instr = imem.read();
      return instr;
   endmethod

   method Action dmem_req(DMem_Req req);
      let phyAddr = (req.addr - dmemSt);
      Bit#(Bits_per_Word_Byte_Index) shift = truncate(req.addr);

      if (shift != 0) begin
         $display("Memory alignment check failed at addr 0x%h", req.addr);
         $finish;
      end

      dmem.a.put(req.written, truncate(phyAddr >> 2), req.data);
      dmem_rd <= !(req.mem_op == MEM_OP_STORE);
      //$display("[DMEM] Addr = 0x%08h", req.addr);
   endmethod

   method ActionValue#(DMem_Resp) dmem_resp;
      Word v = dmem.a.read();

      //$display("[DMEM] Data = 0x%08h", v);
      return dmem_rd ? tagged Valid v : tagged Invalid;
   endmethod
endmodule

// ----------------------------------------------------------------
// This interface is an argument to the 'mkRISCV_Spec' module,
// and is used insided the module to access memory.
// MMUs, caches etc. are outside this boundary.

interface Memory_IFC;
   method Action                  imem_req(Addr addr);
   method ActionValue#(IMem_Resp) imem_resp;

   method Action                  dmem_req(DMem_Req req);
   method ActionValue#(DMem_Resp) dmem_resp;
endinterface

// ================================================================
// This interfacce is offered by the 'mkRISCV_Spec' module to the environment.
// It is not part of the spec, per se, and just has scaffolding that allows
// the environment to control and CPU and probe its state.

interface RISCV_IFC;
   method Action start(Addr initial_pc);

   method ActionValue#(Word) cpuToHost;
endinterface

// ================================================================
// The RISC-V CPU Specification module, 'mkRISCV'

// ----------------
// Default fall-through PC

function Addr fv_fall_through_pc(Addr pc);
   return pc + 4;
endfunction: fv_fall_through_pc

// ----------------

module _mkRISCV#(Bit#(3) cfg_verbose)(RISCV_IFC);

   // CPU state
   Reg#(Bool)  cpu_enabled <- mkReg(False);

   // Program counter
   Reg#(Addr)     rg_FetchPC  <- mkRegU;
   RWire#(Addr)   rw_nxtPC    <- mkRWire;
   RWire#(Addr)   rw_jmpPC    <- mkUnsafeRWire;
   Reg#(Word)     pcEpoch     <- mkConfigRegU;

   // General Purpose Registers
   RegFile#(RegName, Word) rf_GPRs  <- mkRegFileWCF(0, ~0);

   // CSRs
   Reg#(Bit#(64))    csr_cycle   <- mkConfigReg(0);
   Reg#(Bit#(64))    csr_instret <- mkConfigReg(0);

   // internal components
   Memory_IFC           memory   <- mkMemory(csr_cycle);

   // ----------------
   // These CSRs are technically not present in the user-mode ISA.

   Reg#(Word)  csr_mepc       <- mkRegU;
   Reg#(Word)  csr_mcause     <- mkRegU;
   Reg#(Word)  csr_mbadaddr   <- mkRegU;

   Reg#(Maybe#(Word)) csr_dcsr <- mkDReg(tagged Invalid);

   // ----------------------------------------------------------------
   // Non-architectural state, for this model

   Reg#(Maybe#(Addr))      rg_f2d      <- mkDReg(tagged Invalid);
   FIFOF#(Decoded_Instr)   fifo_d2e    <- mkPipelineFIFOF;
   FIFOF#(Exec2Wb_t)       fifo_e2w    <- mkPipelineFIFOF;

   // ----------------------------------------------------------------
   // Scoreboard map
   Reg#(Bool)        rg_scoreGPRs[numRegs];
   for(Integer i = 0; i < numRegs; i = i + 1)
      rg_scoreGPRs[i] <- mkReg(False);
   RWire#(RegName)   rw_scoreGPRsSet   <- mkRWire;
   RWire#(RegName)   rw_scoreGPRsReset <- mkRWire;

   // ----------------------------------------------------------------
   // Read a CSR
   // If the addr is valid, return tagged Valid value
   // else return tagged Invalid

   function Maybe#(Word) fv_read_csr(CSR_Addr csr_addr);
      if      (csr_addr == csr_CYCLE)     return tagged Valid truncate   (csr_cycle);
      else if (csr_addr == csr_INSTRET)   return tagged Valid truncate   (csr_instret);

      else if (csr_addr == csr_CYCLEH  )  return tagged Valid truncateLSB(csr_cycle);
      else if (csr_addr == csr_INSTRETH)  return tagged Valid truncateLSB(csr_instret);

      else if (csr_addr == csr_DCSR)      return tagged Valid 0 ;

      else return tagged Invalid;
   endfunction: fv_read_csr

   // ----------------------------------------------------------------
   // Write a CSR
   // We assume a valid csr_addr, since this is always preceded by a read_csr which performs the check

   function Action fa_write_csr(CSR_Addr csr_addr, Word csr_value);
      action
         if (csr_addr == csr_DCSR) begin
            csr_dcsr <= tagged Valid csr_value;
         end

         else begin
            $display("ERROR: fa_write_csr: (csr_addr 0x%0h, csr_value 0x%0h): illegal csr_addr", csr_addr, csr_value);
            $finish;
         end
      endaction
   endfunction: fa_write_csr

   // ================================================================
   // Instruction execution

   // ----------------------------------------------------------------
   // The following functions are common idioms for finishing an instruction

   // ----------------
   // Finish exception: record exception cause info, go to ENV_CALL state

   function Action fa_finish_with_exception(Bit#(4) exc_code, Addr badaddr);
      action
         if (cfg_verbose != 0) begin
            $display("[%7d] fa_do_exception: epc = 0x%0h, exc_code = 0x%0h, badaddr = 0x%0h", csr_cycle, pcEpoch, exc_code, badaddr);
         end

         csr_mepc     <= pcEpoch;
         csr_mcause   <= { 1'b0, 0, exc_code };
         csr_mbadaddr <= badaddr;

         $finish;
      endaction
   endfunction

   // ----------------
   // Finish instr with no output (no Rd-write): set PC, go to FETCH state

   function Action fa_finish_with_no_output();
      action
      endaction
   endfunction

   // ----------------
   // Finish instr with Rd-write: set Rd, set PC, go to WRITE_BACK state

   function Action fa_finish_with_Rd(RegName rd, Word rd_value);
      action
         fifo_e2w.enq( Exec2Wb_t {rd: rd, rd_value: tagged Value rd_value} );
         fa_finish_with_no_output;
      endaction
   endfunction

   // ----------------
   // Finish instr with Rd-write: set Rd, set PC, go to WRITE_BACK state

   function Action fa_finish_with_Ld(RegName rd, Bit#(3) funct3, Bit#(Bits_per_Word_Byte_Index) align);
      action
         fifo_e2w.enq( Exec2Wb_t {rd: rd, rd_value: (tagged MemOp {funct3: funct3, align: align})} );
         fa_finish_with_no_output;
      endaction
   endfunction

   // ----------------
   // Finish jump instrs; write Rd, set PC, go to FETCH state

   function Action fa_finish_jump(RegName rd, Word rd_value, Addr next_pc);
      action
         fifo_e2w.enq( Exec2Wb_t {rd: rd, rd_value: tagged Value rd_value} );
         rw_jmpPC.wset(next_pc);
      endaction
   endfunction

   // ----------------
   // Finish conditional branch instr: set PC, go to FETCH state

   function Action fa_finish_cond_branch(Bool condition_taken, Addr next_pc);
      action
         if (condition_taken) begin
            rw_jmpPC.wset(next_pc);
         end
      endaction
   endfunction

   // ----------------------------------------------------------------
   // Instruction execution
   // This function encapsulates ALL the opcodes.
   // It has internal functions that group related sub-opcodes.

   function Action fa_exec(Decoded_Instr decoded, Decoded_Fields fields);
      action

         // Values of Rs1 and Rs2 fields of the instr, unsigned
         //Word v1 = decoded.v1;
         //Word v2 = decoded.v2;
         Word  v1  = ((fields.rs1 == 0) ? 0: rf_GPRs.sub(fields.rs1));
         Word  v2  = ((fields.rs2 == 0) ? 0: rf_GPRs.sub(fields.rs2));

         // Values of Rs1 and Rs2 fields of the instr, signed versions
         Word_S  s_v1 = unpack(v1);
         Word_S  s_v2 = unpack(v2);

         // Value of CSR field of instr (if a valid CSR address)
         Maybe #(Word) m_v_csr = fv_read_csr(fields.csr);

         // ----------------------------------------------------------------
         // Instructions for Upper Immediate
         function Action fa_exec_LUI();
            action
               Bit#(32)    v32   = { fields.imm20_U, 12'h0 };
               Word_S      iv    = extend(unpack(v32));
               let         value = pack(iv);

               fa_finish_with_Rd(fields.rd, value);
               if (cfg_verbose > 2) $display("[%7d] fa_exec  : pc = 0x%h, *** lui %s, 0x%h", csr_cycle, decoded.pc, regNameABI[fields.rd], value[31:12]);
            endaction
         endfunction: fa_exec_LUI

         function Action fa_exec_AUIPC();
            action
               Word_S  iv    = extend(unpack({ fields.imm20_U, 12'b0}));
               Word_S  pc_s  = unpack(pcEpoch);
               Word    value = pack(pc_s + iv);

               fa_finish_with_Rd(fields.rd, value);
               if (cfg_verbose > 2) $display("[%7d] fa_exec  : pc = 0x%h, *** auipc %s, 0x%h", csr_cycle, decoded.pc, regNameABI[fields.rd], value[31:12]);
            endaction
         endfunction: fa_exec_AUIPC

         // ----------------------------------------------------------------
         // Instructions for control-transfer

         function Action fa_exec_JAL();
            action
               Word_S offset  = extend(unpack(fields.imm21_UJ));
               Addr   next_pc = pack(unpack(pcEpoch) + offset);

               fa_finish_jump(fields.rd, fv_fall_through_pc(pcEpoch), next_pc);
               if (cfg_verbose > 2) $display("[%7d] fa_exec  : pc = 0x%h, *** jal %s, 0x%h", csr_cycle, decoded.pc, regNameABI[fields.rd], next_pc);
            endaction
         endfunction: fa_exec_JAL

         function Action fa_exec_JALR();
            action
               Word_S offset  = extend(unpack(fields.imm12_I));
               Addr   next_pc = {truncateLSB(pack(s_v1 + offset)), 1'b0};

               fa_finish_jump(fields.rd, fv_fall_through_pc(pcEpoch), next_pc);
               if (cfg_verbose > 2) $display("[%7d] fa_exec  : pc = 0x%h, *** jalr %s, %s, %1d", csr_cycle, decoded.pc, regNameABI[fields.rd], regNameABI[fields.rs1], offset);
            endaction
         endfunction: fa_exec_JALR

         function Action fa_exec_BRANCH(BrFunc op);
            action
               Word_S offset  = extend(unpack(fields.imm13_SB));
               Word   next_pc = pack(unpack(pcEpoch) + offset);

               case(op)
                  Eq    :  fa_finish_cond_branch(v1  == v2,    next_pc);
                  Neq   :  fa_finish_cond_branch(v1  != v2,    next_pc);
                  Lt    :  fa_finish_cond_branch(s_v1 <  s_v2, next_pc);
                  Ge    :  fa_finish_cond_branch(s_v1 >= s_v2, next_pc);
                  Ltu   :  fa_finish_cond_branch(v1  <  v2,    next_pc);
                  /* Geu */
               default  :  fa_finish_cond_branch(v1  >= v2,    next_pc);
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** B", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format(" %s, %s, 0x%h", regNameABI[fields.rd], regNameABI[fields.rs1], next_pc);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_BRANCH

         // ----------------------------------------------------------------
         // LD and ST instructions.
         // Issue request here; will be completed in STATE_EXEC_LD/ST_RESPONSE

         function Action fa_exec_LD_Req(LdFunc op);
            action
               Word_S  imm_s    = extend(unpack(fields.imm12_I));
               Word    mem_addr = pack(s_v1 + imm_s);

               function Action fa_LD_Req(Mem_Data_Size sz);
                  action
                     Bit#(Bits_per_Word_Byte_Index) align = truncate(mem_addr);
                     let req = DMem_Req {mem_op:      MEM_OP_LOAD,
                                         addr:        {mem_addr[xlen-1:2], 2'b00},
                                         written:     0,
                                         data:        ?};
                     memory.dmem_req(req);
                     fa_finish_with_Ld(fields.rd, fields.funct3, align);
                  endaction
               endfunction

               case(op)
                  Lb    :  fa_LD_Req(BITS8);
                  Lbu   :  fa_LD_Req(BITS8);
                  Lh    :  fa_LD_Req(BITS16);
                  Lhu   :  fa_LD_Req(BITS16);
                  /*Lw*/
               default  :  fa_LD_Req(BITS32);
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format(" %s, %s, %1d", regNameABI[fields.rd], regNameABI[fields.rs1], imm_s);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_LD_Req

         function Action fa_exec_ST_Req(StFunc op);
            action
               Word_S  imm_s    = extend(unpack(fields.imm12_S));
               Word    mem_addr = pack(s_v1 + imm_s);

               function Action fa_ST_req(Mem_Data_Size sz);
                  action
                     Bit#(Bits_per_Word_Byte_Index) align = truncate(mem_addr);
                     Word aligned_data = v2 << {align, 3'b0};
                     Bit#(Bytes_per_Word) write_en = (case(sz)
                                                         BITS8:  ('b0001 << align);
                                                         BITS16: ('b0011 << align);
                                                         default/*BITS32*/: ('b1111);
                                                      endcase);

                     let req = DMem_Req {mem_op:      MEM_OP_STORE,
                                         addr:        {mem_addr[xlen-1:2], 2'b00},
                                         data:        aligned_data,
                                         written:     write_en};
                     memory.dmem_req(req);
                     fa_finish_with_no_output;
                  endaction
               endfunction

               case(op)
                  Sb    :  fa_ST_req(BITS8);
                  Sh    :  fa_ST_req(BITS16);
                  /*Sw*/
               default  :  fa_ST_req(BITS32);
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format(" %s, %s, %1d", regNameABI[fields.rd], regNameABI[fields.rs1], imm_s);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_ST_Req

         // ----------------------------------------------------------------
         // Instructios for Register-Immediate alu ops

         function Action fa_exec_OP_IMM(AluFunc op);
            action
               Word                v2    = zeroExtend(fields.imm12_I);
               Word_S              s_v2  = signExtend(unpack(fields.imm12_I));
               Bit#(TLog#(XLEN))   shamt = truncate(fields.imm12_I);

               case(op)
                  Add   :  fa_finish_with_Rd(fields.rd, pack(s_v1 + s_v2));
                  Slt   :  fa_finish_with_Rd(fields.rd, ((s_v1 < s_v2) ? 1 : 0));
                  Sltu  :  fa_finish_with_Rd(fields.rd, ((v1  < pack(s_v2))  ? 1 : 0));
                  Xor   :  fa_finish_with_Rd(fields.rd, pack(s_v1 ^ s_v2));
                  Or    :  fa_finish_with_Rd(fields.rd, pack(s_v1 | s_v2));
                  And   :  fa_finish_with_Rd(fields.rd, pack(s_v1 & s_v2));
                  Sll   :  fa_finish_with_Rd(fields.rd, (v1 << shamt));
                  Srl   :  fa_finish_with_Rd(fields.rd, (v1 >> shamt));
                  /*Sra*/
               default  :  fa_finish_with_Rd(fields.rd, pack(s_v1 >> shamt));
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format("i %s, %s, 0x%h", regNameABI[fields.rd], regNameABI[fields.rs1], fields.imm12_I);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_OP_IMM

         // ----------------------------------------------------------------
         // Instructios for Register-Register alu ops

         function Action fa_exec_OP(AluFunc op);
            action
               Bit#(TLog#(XLEN)) shamt = truncate(v2);    // NOTE: upper bits are unspecified in spec

               case(op)
                  Add   :  fa_finish_with_Rd(fields.rd, pack(s_v1 + s_v2));
                  Sub   :  fa_finish_with_Rd(fields.rd, pack(s_v1 - s_v2));
                  Sll   :  fa_finish_with_Rd(fields.rd, (v1 << shamt));
                  Slt   :  fa_finish_with_Rd(fields.rd, ((s_v1 < s_v2) ? 1 : 0));
                  Sltu  :  fa_finish_with_Rd(fields.rd, ((v1  < v2)  ? 1 : 0));
                  Xor   :  fa_finish_with_Rd(fields.rd, pack(s_v1 ^ s_v2));
                  Srl   :  fa_finish_with_Rd(fields.rd, (v1 >> shamt));
                  Sra   :  fa_finish_with_Rd(fields.rd, pack(s_v1 >> shamt));
                  Or    :  fa_finish_with_Rd(fields.rd, pack(s_v1 | s_v2));
                  /*And*/
               default  :  fa_finish_with_Rd(fields.rd, pack(s_v1 & s_v2));
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format(" %s, %s, %s", regNameABI[fields.rd], regNameABI[fields.rs1], regNameABI[fields.rs2]);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_OP

         // ----------------------------------------------------------------
         // Instrucions for System-level ops

         function Action fa_exec_SYSTEM(SysFunc op);
            action
               let csr_old_val = fromMaybe(?, m_v_csr);

               case(op)
                  CSRRW :  begin
                                 fa_write_csr(fields.csr, v1);
                                 fa_finish_with_Rd(fields.rd, csr_old_val);
                              end

                  CSRRS :  begin
                                 if (fields.rs1 != 0) begin
                                    Word csr_new_val = (csr_old_val | v1);
                                    fa_write_csr(fields.csr, csr_new_val);
                                 end
                                 fa_finish_with_Rd(fields.rd, csr_old_val);
                              end

                  /*CSRRC*/
               default  :  begin
                                 if (fields.rs1 != 0) begin
                                    Word csr_new_val = (csr_old_val & (~ v1));
                                    fa_write_csr(fields.csr, csr_new_val);
                                 end
                                 fa_finish_with_Rd(fields.rd, csr_old_val);
                              end
               endcase

               if (cfg_verbose > 2) begin
                  if ( (op == CSRRS) && (fields.csr == csr_CYCLE) )
                     $display("[%7d] fa_exec  : pc = 0x%h, *** rdcycle %s", csr_cycle, decoded.pc, regNameABI[fields.rd]);
                  else if ( (op == CSRRS) && (fields.csr == csr_INSTRET) )
                     $display("[%7d] fa_exec  : pc = 0x%h, *** rdinstret %s", csr_cycle, decoded.pc, regNameABI[fields.rd]);
                  else if ( (op == CSRRW) && (fields.csr == csr_DCSR) )
                     $display("[%7d] fa_exec  : pc = 0x%h, *** csrw dcsr, %s", csr_cycle, decoded.pc, regNameABI[fields.rs1]);
                  else begin
                     let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                              + fshow(op)
                              + $format(" %s, 0x%h, %s", regNameABI[fields.rd], fields.csr, regNameABI[fields.rs1]);
                     $display(msg);
                  end
               end
            endaction
         endfunction: fa_exec_SYSTEM

         // ----------------------------------------------------------------
         // Main body of fa_exec(), dispatching to the sub functions
         // based on major OPCODE

         case(decoded.op.opcode) matches
            tagged Lui        :  fa_exec_LUI();
            tagged Auipc      :  fa_exec_AUIPC();
            tagged Jal        :  fa_exec_JAL();
            tagged Jalr       :  fa_exec_JALR();
            tagged Br   .op   :  fa_exec_BRANCH(op);
            tagged Ld   .op   :  fa_exec_LD_Req(op);
            tagged St   .op   :  fa_exec_ST_Req(op);
            tagged Alui .op   :  fa_exec_OP_IMM(op);
            tagged Alu  .op   :  fa_exec_OP(op);
            tagged Sys  .op   :  fa_exec_SYSTEM(op);
            default           :  fa_finish_with_exception(exc_code_ILLEGAL_INSTRUCTION, ?);
         endcase
      endaction
   endfunction: fa_exec

   // ================================================================
   // The CPU's top-level logic

   // Update CPU internal status in every cycle
   (* fire_when_enabled, no_implicit_conditions *)
   rule update_score;
      let x = fromMaybe(0, rw_scoreGPRsSet.wget);
      let y = fromMaybe(0, rw_scoreGPRsReset.wget);

      rg_scoreGPRs[x] <= True;
      if (x != y) begin
         rg_scoreGPRs[y] <= False;
      end
   endrule

   // ---------------- FETCH
   // Issue instruction request and decode

   // ----------------------------------------------------------------
   // Instruction fetch
   (* fire_when_enabled *)
   rule rl_fetch(cpu_enabled);
      let next_pc = fromMaybe(fromMaybe(rg_FetchPC, rw_nxtPC.wget), rw_jmpPC.wget);
      if (cfg_verbose > 1) $display("[%7d] rl_fetch : Read instruction pc = 0x%08h", csr_cycle, next_pc);
      memory.imem_req(next_pc);
      rg_f2d      <= tagged Valid next_pc;
      rg_FetchPC  <= next_pc;
   endrule

   // ----------------------------------------------------------------
   // Instruction decode
   (* conflict_free="rl_decode, rl_exec" *)
   rule rl_decode(fifo_d2e.notFull &&& rg_f2d matches tagged Valid .xPC);
      let instr <- memory.imem_resp;

      if (cfg_verbose > 1) $display("[%7d] rl_decode: pc = 0x%08h, instr = %h", csr_cycle, xPC, instr);
      Decoded_Instr  decoded = fv_decode(xPC, instr);

      fifo_d2e.enq( decoded );
      rw_nxtPC.wset(fv_fall_through_pc(xPC));
   endrule

   // ---------------- EXECUTE
   // Receive instruction from IMem; handle exception if any, else execute it;

   //(* no_implicit_conditions *)
   rule rl_exec(fifo_d2e.notEmpty && fifo_e2w.notFull);
      let decoded = fifo_d2e.first;

      // ----------------------------------------------------------------
      // Instruction fields decode
      Decoded_Fields fields   = fv_decode_fields(decoded.instr);

      // Calculate dependency register
      let score1 = False;
      let score2 = False;

      if (decoded.op.rs1 matches tagged Valid .x &&& x != 0)
         score1 = rg_scoreGPRs[x];

      if (decoded.op.rs2 matches tagged Valid .x &&& x != 0)
         score2 = rg_scoreGPRs[x];

      let score_conflict = score1 || score2;

      if (cfg_verbose > 2) begin
         $write("[%7d] rl_exec  : Scoreboard = [", csr_cycle);
         for(Integer i = 0; i < numRegs; i = i + 1) begin
            $write("%1d, ", rg_scoreGPRs[i] ? 1 : 0);
         end
         $write("], dst = %d, rs1 = %d, rs2 = %d\n", fields.rd, fields.rs1, fields.rs2);
      end

      if (decoded.pc != pcEpoch) begin
         if (cfg_verbose > 1) $display("[%7d] rl_exec  : STALL Ignore pc = 0x%08h, instr = 0x%08h, epoch = 0x%08h", csr_cycle, decoded.pc, decoded.instr, pcEpoch);
         fifo_d2e.deq;
      end
      else if (score_conflict) begin
         if (cfg_verbose > 1) $display("[%7d] rl_exec  : STALL Conflict pc = 0x%08h, instr = 0x%h, epoch = 0x%08h", csr_cycle, decoded.pc, decoded.instr, pcEpoch);
      end
      else begin
         if (cfg_verbose > 1) $display("[%7d] rl_exec  : pc = 0x%08h, instr = 0x%08h, epoch = 0x%08h", csr_cycle, decoded.pc, decoded.instr, pcEpoch);

         Bool checkScore = case(decoded.op.opcode) matches
                              tagged Br .*   : False;
                              tagged St .*   : False;
                              default        : True;
                           endcase;

         // Update dependency flag for $rd
         if (checkScore) begin
            rw_scoreGPRsSet.wset(fields.rd);
         end

         fa_exec(decoded, fields);
         fifo_d2e.deq;

         // Update pcEpoch
         pcEpoch <= fromMaybe(fv_fall_through_pc(pcEpoch), rw_jmpPC.wget);

         // ---------------- FINISH: increment csr_instret or record explicit CSRRx update of csr_instret
         csr_instret <= csr_instret + 1;
      end
   endrule

   // ---------------- RegFile & DMem Write Back
   rule rl_write_back(fifo_e2w.notEmpty);
      let x = fifo_e2w.first;
      fifo_e2w.deq;
      let rd = x.rd;
      let rd_value = ?;

      case(x.rd_value) matches
         tagged MemOp .op: begin
            let funct3 = op.funct3;
            let align = op.align;
            let resp <- memory.dmem_resp;

            if (cfg_verbose > 0 && !isValid(resp)) begin
               $display("[%7d] rl_write : Memory read failed", csr_cycle);
               $finish;
            end

            let u = fromMaybe(?, resp);
            let data = u >> {align, 3'b0};
            let extendFunc = (funct3 == f3_LBU || funct3 == f3_LHU) ? zeroExtend : signExtend;
            rd_value = (case (funct3)
                    f3_LB, f3_LBU: extendFunc(data[7:0]);
                    f3_LH, f3_LHU: extendFunc(data[15:0]);
                    default: extendFunc(data[31:0]);
                  endcase);
         end
         tagged Value .value: begin
            rd_value = value;
         end
      endcase

      if (cfg_verbose > 1) $display("[%7d] rl_write : %s = %h, clear scoreGPRs[%1d] (= %1d)", csr_cycle, regNameABI[rd], rd_value, rd, rg_scoreGPRs[rd] ? 1 : 0);

      // NOTE: DOES NOT check register x0 because set value to Zero when read
      rf_GPRs.upd(rd, rd_value);
      rw_scoreGPRsReset.wset(rd);
   endrule

   // ---------------- Increment csr_cycle according to external oracles

   rule rl_incr_cycle;
      csr_cycle <= csr_cycle + 1;
   endrule

   // ----------------------------------------------------------------
   // INTERFACE

   method Action start(Addr initial_pc) if (!cpu_enabled);
      rg_FetchPC  <= initial_pc;
      pcEpoch     <= initial_pc;
      cpu_enabled <= True;
   endmethod

   method ActionValue#(Word) cpuToHost() if (csr_dcsr matches tagged Valid .ret);
      return ret;
   endmethod
endmodule

// ================================================================

(* synthesize *)
module mkRISCV(RISCV_IFC);
   (* hide *) let _m <- _mkRISCV(0);
   return _m;
endmodule

// ================================================================
`ifdef TEST_BENCH_RISCV
module mkTb();
   Reg#(Bit#(32))    cycles <- mkConfigReg(0);

   let               dut         <- mkRISCV;
   Reg#(Bit#(16))    csr_int_low <- mkRegU;

   rule do_cycle;
      cycles <= cycles + 1;
      //if (cycles > 1200)
      //   $finish;
   endrule

   rule do_cpuToHost;
      let csr_value <- dut.cpuToHost;
      Bit#(16) csrCmd = truncateLSB(csr_value);
      Bit#(16) csrDat = truncate(csr_value);

      case(csrCmd)
         0: begin // Exit
            if (csrDat == 0) begin
               $fdisplay(stderr, "PASSED\n");
            end
            else begin
               $fdisplay(stderr, "FAILED: exit code = %d\n", csrDat);
            end
            $finish;
         end
         1: begin // PrintChar
            $fwrite(stderr, "%c", csrDat[7:0]);
         end
         2: begin // PrintIntLow
            csr_int_low <= csrDat;
         end
         3: begin // PrintIntHigh
            $fwrite(stderr, "%d", {csrDat, csr_int_low});
         end
         default: begin
            $fdisplay(stderr, "Unknown type %d", csrCmd);
            $finish;
         end
      endcase
   endrule

   rule do_start(cycles == 0);
      dut.start('h200);
   endrule
endmodule
`endif // TESTBENCH

// Copyright (c) 2017 Min Chen.  All Rights Reserved.
// Author: Min Chen

// ================================================================
// BSV library imports

import RegFile       :: *;    // For RISC-V GPRs
import ConfigReg     :: *;
import FIFOF         :: *;
import SpecialFIFOs  :: *;

// ================================================================
// BSV project imports

import ISA_Decls :: *;    // Instruction encodings

// ================================================================
// Memory interface for CPU
// ================================================================

import BRAMCore :: *;
import DReg     :: *;

// ----------------
// IMem responses: either and exception or an instruction

typedef Word IMem_Resp;

// ----------------
// DMem request ops and sizes

typedef enum {
   MEM_OP_LOAD,
   MEM_OP_STORE
} Mem_Op deriving(Eq, Bits, FShow);

// ----------------
// DMem requests

typedef struct {
   Mem_Op               mem_op;
   Addr                 addr;
   Word                 data;          // Only relevant if mem_op == MEM_OP_STORE
   Bit#(Bytes_per_Word) written;
} DMem_Req deriving(Bits);

// ----------------
// DMem responses: either an exception or data
// (data value is only relevant for LOADs, irrelevant for STOREs)

typedef Maybe#(Word) DMem_Resp;

// ----------------
// Memory interface reference design

module mkMemory#(Reg#(Bit#(64)) cycles)(Memory_IFC);
   BRAM_PORT#(Bit#(14), Word)             imem <- mkBRAMCore1Load(valueOf(TExp#(14)), False, (genC ? "mem.vmh" : "R:/mem.vmh"), False);
   BRAM_DUAL_PORT_BE#(Bit#(15), Word, 4)  dmem <- mkBRAMCore2BELoad(valueOf(TExp#(15)), False, (genC ? "mem.vmh.D" : "R:/mem.vmh.D"), False);
   Reg#(Bool)                             dmem_rd  <- mkDReg(False);
   Reg#(Bit#(2))                          rg_shift <- mkRegU;

   method Action imem_req(Addr addr);
      let phyAddr = addr - imemSt;
      imem.put(False, truncate(phyAddr >> 2), ?);
      //$display("[%7d] [IMEM] ReqAddr = 0x%08h", cycles, addr);
   endmethod

   method ActionValue#(IMem_Resp) imem_resp();
      let instr = imem.read();
      return instr;
   endmethod

   method Action dmem_req(DMem_Req req);
      let phyAddr = (req.addr - dmemSt);
      Bit#(Bits_per_Word_Byte_Index) shift = truncate(req.addr);

      if (shift != 0) begin
         $display("Memory alignment check failed at addr 0x%h", req.addr);
         $finish;
      end

      dmem.a.put(req.written, truncate(phyAddr >> 2), req.data);
      dmem_rd <= !(req.mem_op == MEM_OP_STORE);
      //$display("[DMEM] Addr = 0x%08h", req.addr);
   endmethod

   method ActionValue#(DMem_Resp) dmem_resp;
      Word v = dmem.a.read();

      //$display("[DMEM] Data = 0x%08h", v);
      return dmem_rd ? tagged Valid v : tagged Invalid;
   endmethod
endmodule

// ----------------------------------------------------------------
// This interface is an argument to the 'mkRISCV_Spec' module,
// and is used insided the module to access memory.
// MMUs, caches etc. are outside this boundary.

interface Memory_IFC;
   method Action                  imem_req(Addr addr);
   method ActionValue#(IMem_Resp) imem_resp;

   method Action                  dmem_req(DMem_Req req);
   method ActionValue#(DMem_Resp) dmem_resp;
endinterface

// ================================================================
// This interfacce is offered by the 'mkRISCV_Spec' module to the environment.
// It is not part of the spec, per se, and just has scaffolding that allows
// the environment to control and CPU and probe its state.

interface RISCV_IFC;
   method Action start(Addr initial_pc);

   method ActionValue#(Word) cpuToHost;
endinterface

// ================================================================
// The RISC-V CPU Specification module, 'mkRISCV'

// ----------------
// Default fall-through PC

function Addr fv_fall_through_pc(Addr pc);
   return pc + 4;
endfunction: fv_fall_through_pc

// ----------------

module _mkRISCV#(Bit#(3) cfg_verbose)(RISCV_IFC);

   // CPU state
   Reg#(Bool)  cpu_enabled <- mkReg(False);

   // Program counter
   Reg#(Addr)     pc       <- mkRegU;
   RWire#(Addr)   rw_nxtPC <- mkRWire;
   RWire#(Addr)   rw_jmpPC <- mkUnsafeRWire;
   Reg#(Word)     pcEpoch  <- mkConfigRegU;

   // General Purpose Registers
   RegFile#(RegName, Word) rf_GPRs  <- mkRegFileWCF(0, ~0);

   // CSRs
   Reg#(Bit#(64))    csr_cycle   <- mkConfigReg(0);
   Reg#(Bit#(64))    csr_instret <- mkConfigReg(0);

   // internal components
   Memory_IFC           memory   <- mkMemory(csr_cycle);

   // ----------------
   // These CSRs are technically not present in the user-mode ISA.

   Reg#(Word)  csr_mepc       <- mkRegU;
   Reg#(Word)  csr_mcause     <- mkRegU;
   Reg#(Word)  csr_mbadaddr   <- mkRegU;

   Reg#(Maybe#(Word)) csr_dcsr <- mkDReg(tagged Invalid);

   // ----------------------------------------------------------------
   // Non-architectural state, for this model

   Reg#(Maybe#(Addr))      rg_f2d      <- mkDReg(tagged Invalid);
   FIFOF#(Decoded_Instr)   fifo_d2e    <- mkPipelineFIFOF;
   FIFOF#(Exec2Wb_t)       fifo_e2w    <- mkPipelineFIFOF;

   // ----------------------------------------------------------------
   // Scoreboard map
   Reg#(Bool)        rg_scoreGPRs[numRegs];
   for(Integer i = 0; i < numRegs; i = i + 1)
      rg_scoreGPRs[i] <- mkReg(False);
   RWire#(RegName)   rw_scoreGPRsSet   <- mkRWire;
   RWire#(RegName)   rw_scoreGPRsReset <- mkRWire;

   // ----------------------------------------------------------------
   // Read a CSR
   // If the addr is valid, return tagged Valid value
   // else return tagged Invalid

   function Maybe#(Word) fv_read_csr(CSR_Addr csr_addr);
      if      (csr_addr == csr_CYCLE)     return tagged Valid truncate   (csr_cycle);
      else if (csr_addr == csr_INSTRET)   return tagged Valid truncate   (csr_instret);

      else if (csr_addr == csr_CYCLEH  )  return tagged Valid truncateLSB(csr_cycle);
      else if (csr_addr == csr_INSTRETH)  return tagged Valid truncateLSB(csr_instret);

      else if (csr_addr == csr_DCSR)      return tagged Valid 0 ;

      else return tagged Invalid;
   endfunction: fv_read_csr

   // ----------------------------------------------------------------
   // Write a CSR
   // We assume a valid csr_addr, since this is always preceded by a read_csr which performs the check

   function Action fa_write_csr(CSR_Addr csr_addr, Word csr_value);
      action
         if (csr_addr == csr_DCSR) begin
            csr_dcsr <= tagged Valid csr_value;
         end

         else begin
            $display("ERROR: fa_write_csr: (csr_addr 0x%0h, csr_value 0x%0h): illegal csr_addr", csr_addr, csr_value);
            $finish;
         end
      endaction
   endfunction: fa_write_csr

   // ================================================================
   // Instruction execution

   // ----------------------------------------------------------------
   // The following functions are common idioms for finishing an instruction

   // ----------------
   // Finish exception: record exception cause info, go to ENV_CALL state

   function Action fa_finish_with_exception(Bit#(4) exc_code, Addr badaddr);
      action
         if (cfg_verbose != 0) begin
            $display("[%7d] fa_do_exception: epc = 0x%0h, exc_code = 0x%0h, badaddr = 0x%0h", csr_cycle, pcEpoch, exc_code, badaddr);
         end

         csr_mepc     <= pcEpoch;
         csr_mcause   <= { 1'b0, 0, exc_code };
         csr_mbadaddr <= badaddr;

         $finish;
      endaction
   endfunction

   // ----------------
   // Finish instr with no output (no Rd-write): set PC, go to FETCH state

   function Action fa_finish_with_no_output();
      action
      endaction
   endfunction

   // ----------------
   // Finish instr with Rd-write: set Rd, set PC, go to WRITE_BACK state

   function Action fa_finish_with_Rd(RegName rd, Word rd_value);
      action
         fifo_e2w.enq( Exec2Wb_t {rd: rd, rd_value: tagged Value rd_value} );
         fa_finish_with_no_output;
      endaction
   endfunction

   // ----------------
   // Finish instr with Rd-write: set Rd, set PC, go to WRITE_BACK state

   function Action fa_finish_with_Ld(RegName rd, Bit#(3) funct3, Bit#(Bits_per_Word_Byte_Index) align);
      action
         fifo_e2w.enq( Exec2Wb_t {rd: rd, rd_value: (tagged MemOp {funct3: funct3, align: align})} );
         fa_finish_with_no_output;
      endaction
   endfunction

   // ----------------
   // Finish jump instrs; write Rd, set PC, go to FETCH state

   function Action fa_finish_jump(RegName rd, Word rd_value, Addr next_pc);
      action
         fifo_e2w.enq( Exec2Wb_t {rd: rd, rd_value: tagged Value rd_value} );
         rw_jmpPC.wset(next_pc);
      endaction
   endfunction

   // ----------------
   // Finish conditional branch instr: set PC, go to FETCH state

   function Action fa_finish_cond_branch(Bool condition_taken, Addr next_pc);
      action
         if (condition_taken) begin
            rw_jmpPC.wset(next_pc);
         end
      endaction
   endfunction

   // ----------------------------------------------------------------
   // Instruction execution
   // This function encapsulates ALL the opcodes.
   // It has internal functions that group related sub-opcodes.

   function Action fa_exec(Decoded_Instr decoded, Decoded_Fields fields);
      action

         // Values of Rs1 and Rs2 fields of the instr, unsigned
         //Word v1 = decoded.v1;
         //Word v2 = decoded.v2;
         Word  v1  = ((fields.rs1 == 0) ? 0: rf_GPRs.sub(fields.rs1));
         Word  v2  = ((fields.rs2 == 0) ? 0: rf_GPRs.sub(fields.rs2));

         // Values of Rs1 and Rs2 fields of the instr, signed versions
         Word_S  s_v1 = unpack(v1);
         Word_S  s_v2 = unpack(v2);

         // Value of CSR field of instr (if a valid CSR address)
         Maybe #(Word) m_v_csr = fv_read_csr(fields.csr);

         // ----------------------------------------------------------------
         // Instructions for Upper Immediate
         function Action fa_exec_LUI();
            action
               Bit#(32)    v32   = { fields.imm20_U, 12'h0 };
               Word_S      iv    = extend(unpack(v32));
               let         value = pack(iv);

               fa_finish_with_Rd(fields.rd, value);
               if (cfg_verbose > 2) $display("[%7d] fa_exec  : pc = 0x%h, *** lui %s, 0x%h", csr_cycle, decoded.pc, regNameABI[fields.rd], value[31:12]);
            endaction
         endfunction: fa_exec_LUI

         function Action fa_exec_AUIPC();
            action
               Word_S  iv    = extend(unpack({ fields.imm20_U, 12'b0}));
               Word_S  pc_s  = unpack(pcEpoch);
               Word    value = pack(pc_s + iv);

               fa_finish_with_Rd(fields.rd, value);
               if (cfg_verbose > 2) $display("[%7d] fa_exec  : pc = 0x%h, *** auipc %s, 0x%h", csr_cycle, decoded.pc, regNameABI[fields.rd], value[31:12]);
            endaction
         endfunction: fa_exec_AUIPC

         // ----------------------------------------------------------------
         // Instructions for control-transfer

         function Action fa_exec_JAL();
            action
               Word_S offset  = extend(unpack(fields.imm21_UJ));
               Addr   next_pc = pack(unpack(pcEpoch) + offset);

               fa_finish_jump(fields.rd, fv_fall_through_pc(pcEpoch), next_pc);
               if (cfg_verbose > 2) $display("[%7d] fa_exec  : pc = 0x%h, *** jal %s, 0x%h", csr_cycle, decoded.pc, regNameABI[fields.rd], next_pc);
            endaction
         endfunction: fa_exec_JAL

         function Action fa_exec_JALR();
            action
               Word_S offset  = extend(unpack(fields.imm12_I));
               Addr   next_pc = {truncateLSB(pack(s_v1 + offset)), 1'b0};

               fa_finish_jump(fields.rd, fv_fall_through_pc(pcEpoch), next_pc);
               if (cfg_verbose > 2) $display("[%7d] fa_exec  : pc = 0x%h, *** jalr %s, %s, %1d", csr_cycle, decoded.pc, regNameABI[fields.rd], regNameABI[fields.rs1], offset);
            endaction
         endfunction: fa_exec_JALR

         function Action fa_exec_BRANCH(BrFunc op);
            action
               Word_S offset  = extend(unpack(fields.imm13_SB));
               Word   next_pc = pack(unpack(pcEpoch) + offset);

               case(op)
                  Eq    :  fa_finish_cond_branch(v1  == v2,    next_pc);
                  Neq   :  fa_finish_cond_branch(v1  != v2,    next_pc);
                  Lt    :  fa_finish_cond_branch(s_v1 <  s_v2, next_pc);
                  Ge    :  fa_finish_cond_branch(s_v1 >= s_v2, next_pc);
                  Ltu   :  fa_finish_cond_branch(v1  <  v2,    next_pc);
                  /* Geu */
               default  :  fa_finish_cond_branch(v1  >= v2,    next_pc);
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** B", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format(" %s, %s, 0x%h", regNameABI[fields.rd], regNameABI[fields.rs1], next_pc);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_BRANCH

         // ----------------------------------------------------------------
         // LD and ST instructions.
         // Issue request here; will be completed in STATE_EXEC_LD/ST_RESPONSE

         function Action fa_exec_LD_Req(LdFunc op);
            action
               Word_S  imm_s    = extend(unpack(fields.imm12_I));
               Word    mem_addr = pack(s_v1 + imm_s);

               function Action fa_LD_Req(Mem_Data_Size sz);
                  action
                     Bit#(Bits_per_Word_Byte_Index) align = truncate(mem_addr);
                     let req = DMem_Req {mem_op:      MEM_OP_LOAD,
                                         addr:        {mem_addr[xlen-1:2], 2'b00},
                                         written:     0,
                                         data:        ?};
                     memory.dmem_req(req);
                     fa_finish_with_Ld(fields.rd, fields.funct3, align);
                  endaction
               endfunction

               case(op)
                  Lb    :  fa_LD_Req(BITS8);
                  Lbu   :  fa_LD_Req(BITS8);
                  Lh    :  fa_LD_Req(BITS16);
                  Lhu   :  fa_LD_Req(BITS16);
                  /*Lw*/
               default  :  fa_LD_Req(BITS32);
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format(" %s, %s, %1d", regNameABI[fields.rd], regNameABI[fields.rs1], imm_s);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_LD_Req

         function Action fa_exec_ST_Req(StFunc op);
            action
               Word_S  imm_s    = extend(unpack(fields.imm12_S));
               Word    mem_addr = pack(s_v1 + imm_s);

               function Action fa_ST_req(Mem_Data_Size sz);
                  action
                     Bit#(Bits_per_Word_Byte_Index) align = truncate(mem_addr);
                     Word aligned_data = v2 << {align, 3'b0};
                     Bit#(Bytes_per_Word) write_en = (case(sz)
                                                         BITS8:  ('b0001 << align);
                                                         BITS16: ('b0011 << align);
                                                         default/*BITS32*/: ('b1111);
                                                      endcase);

                     let req = DMem_Req {mem_op:      MEM_OP_STORE,
                                         addr:        {mem_addr[xlen-1:2], 2'b00},
                                         data:        aligned_data,
                                         written:     write_en};
                     memory.dmem_req(req);
                     fa_finish_with_no_output;
                  endaction
               endfunction

               case(op)
                  Sb    :  fa_ST_req(BITS8);
                  Sh    :  fa_ST_req(BITS16);
                  /*Sw*/
               default  :  fa_ST_req(BITS32);
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format(" %s, %s, %1d", regNameABI[fields.rd], regNameABI[fields.rs1], imm_s);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_ST_Req

         // ----------------------------------------------------------------
         // Instructios for Register-Immediate alu ops

         function Action fa_exec_OP_IMM(AluFunc op);
            action
               Word                v2    = zeroExtend(fields.imm12_I);
               Word_S              s_v2  = signExtend(unpack(fields.imm12_I));
               Bit#(TLog#(XLEN))   shamt = truncate(fields.imm12_I);

               case(op)
                  Add   :  fa_finish_with_Rd(fields.rd, pack(s_v1 + s_v2));
                  Slt   :  fa_finish_with_Rd(fields.rd, ((s_v1 < s_v2) ? 1 : 0));
                  Sltu  :  fa_finish_with_Rd(fields.rd, ((v1  < pack(s_v2))  ? 1 : 0));
                  Xor   :  fa_finish_with_Rd(fields.rd, pack(s_v1 ^ s_v2));
                  Or    :  fa_finish_with_Rd(fields.rd, pack(s_v1 | s_v2));
                  And   :  fa_finish_with_Rd(fields.rd, pack(s_v1 & s_v2));
                  Sll   :  fa_finish_with_Rd(fields.rd, (v1 << shamt));
                  Srl   :  fa_finish_with_Rd(fields.rd, (v1 >> shamt));
                  /*Sra*/
               default  :  fa_finish_with_Rd(fields.rd, pack(s_v1 >> shamt));
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format("i %s, %s, 0x%h", regNameABI[fields.rd], regNameABI[fields.rs1], fields.imm12_I);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_OP_IMM

         // ----------------------------------------------------------------
         // Instructios for Register-Register alu ops

         function Action fa_exec_OP(AluFunc op);
            action
               Bit#(TLog#(XLEN)) shamt = truncate(v2);    // NOTE: upper bits are unspecified in spec

               case(op)
                  Add   :  fa_finish_with_Rd(fields.rd, pack(s_v1 + s_v2));
                  Sub   :  fa_finish_with_Rd(fields.rd, pack(s_v1 - s_v2));
                  Sll   :  fa_finish_with_Rd(fields.rd, (v1 << shamt));
                  Slt   :  fa_finish_with_Rd(fields.rd, ((s_v1 < s_v2) ? 1 : 0));
                  Sltu  :  fa_finish_with_Rd(fields.rd, ((v1  < v2)  ? 1 : 0));
                  Xor   :  fa_finish_with_Rd(fields.rd, pack(s_v1 ^ s_v2));
                  Srl   :  fa_finish_with_Rd(fields.rd, (v1 >> shamt));
                  Sra   :  fa_finish_with_Rd(fields.rd, pack(s_v1 >> shamt));
                  Or    :  fa_finish_with_Rd(fields.rd, pack(s_v1 | s_v2));
                  /*And*/
               default  :  fa_finish_with_Rd(fields.rd, pack(s_v1 & s_v2));
               endcase

               if (cfg_verbose > 2) begin
                  let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                           + fshow(op)
                           + $format(" %s, %s, %s", regNameABI[fields.rd], regNameABI[fields.rs1], regNameABI[fields.rs2]);
                  $display(msg);
               end
            endaction
         endfunction: fa_exec_OP

         // ----------------------------------------------------------------
         // Instrucions for System-level ops

         function Action fa_exec_SYSTEM(SysFunc op);
            action
               let csr_old_val = fromMaybe(?, m_v_csr);

               case(op)
                  CSRRW :  begin
                                 fa_write_csr(fields.csr, v1);
                                 fa_finish_with_Rd(fields.rd, csr_old_val);
                              end

                  CSRRS :  begin
                                 if (fields.rs1 != 0) begin
                                    Word csr_new_val = (csr_old_val | v1);
                                    fa_write_csr(fields.csr, csr_new_val);
                                 end
                                 fa_finish_with_Rd(fields.rd, csr_old_val);
                              end

                  /*CSRRC*/
               default  :  begin
                                 if (fields.rs1 != 0) begin
                                    Word csr_new_val = (csr_old_val & (~ v1));
                                    fa_write_csr(fields.csr, csr_new_val);
                                 end
                                 fa_finish_with_Rd(fields.rd, csr_old_val);
                              end
               endcase

               if (cfg_verbose > 2) begin
                  if ( (op == CSRRS) && (fields.csr == csr_CYCLE) )
                     $display("[%7d] fa_exec  : pc = 0x%h, *** rdcycle %s", csr_cycle, decoded.pc, regNameABI[fields.rd]);
                  else if ( (op == CSRRS) && (fields.csr == csr_INSTRET) )
                     $display("[%7d] fa_exec  : pc = 0x%h, *** rdinstret %s", csr_cycle, decoded.pc, regNameABI[fields.rd]);
                  else if ( (op == CSRRW) && (fields.csr == csr_DCSR) )
                     $display("[%7d] fa_exec  : pc = 0x%h, *** csrw dcsr, %s", csr_cycle, decoded.pc, regNameABI[fields.rs1]);
                  else begin
                     let msg =  $format("[%7d] fa_exec  : pc = 0x%h, *** ", csr_cycle, decoded.pc)
                              + fshow(op)
                              + $format(" %s, 0x%h, %s", regNameABI[fields.rd], fields.csr, regNameABI[fields.rs1]);
                     $display(msg);
                  end
               end
            endaction
         endfunction: fa_exec_SYSTEM

         // ----------------------------------------------------------------
         // Main body of fa_exec(), dispatching to the sub functions
         // based on major OPCODE

         case(decoded.op.opcode) matches
            tagged Lui        :  fa_exec_LUI();
            tagged Auipc      :  fa_exec_AUIPC();
            tagged Jal        :  fa_exec_JAL();
            tagged Jalr       :  fa_exec_JALR();
            tagged Br   .op   :  fa_exec_BRANCH(op);
            tagged Ld   .op   :  fa_exec_LD_Req(op);
            tagged St   .op   :  fa_exec_ST_Req(op);
            tagged Alui .op   :  fa_exec_OP_IMM(op);
            tagged Alu  .op   :  fa_exec_OP(op);
            tagged Sys  .op   :  fa_exec_SYSTEM(op);
            default           :  fa_finish_with_exception(exc_code_ILLEGAL_INSTRUCTION, ?);
         endcase
      endaction
   endfunction: fa_exec

   // ================================================================
   // The CPU's top-level logic

   // Update CPU internal status in every cycle
   (* fire_when_enabled, no_implicit_conditions *)
   rule update_score;
      let x = fromMaybe(0, rw_scoreGPRsSet.wget);
      let y = fromMaybe(0, rw_scoreGPRsReset.wget);

      rg_scoreGPRs[x] <= True;
      if (x != y) begin
         rg_scoreGPRs[y] <= False;
      end
   endrule

   // ---------------- FETCH
   // Issue instruction request and decode

   // ----------------------------------------------------------------
   // Instruction fetch
   (* fire_when_enabled *)
   rule rl_fetch(cpu_enabled);
      let next_pc = fromMaybe(fromMaybe(pc, rw_nxtPC.wget), rw_jmpPC.wget);
      if (cfg_verbose > 1) $display("[%7d] rl_fetch : Read instruction pc = 0x%08h", csr_cycle, next_pc);
      memory.imem_req(next_pc);
      rg_f2d <= tagged Valid next_pc;
      pc <= next_pc;
   endrule

   // ----------------------------------------------------------------
   // Instruction decode
   (* conflict_free="rl_decode, rl_exec" *)
   rule rl_decode(fifo_d2e.notFull &&& rg_f2d matches tagged Valid .xPC);
      let instr <- memory.imem_resp;

      if (cfg_verbose > 1) $display("[%7d] rl_decode: pc = 0x%08h, instr = %h", csr_cycle, xPC, instr);
      Decoded_Instr  decoded = fv_decode(xPC, instr);

      fifo_d2e.enq( decoded );
      rw_nxtPC.wset(fv_fall_through_pc(xPC));
   endrule

   // ---------------- EXECUTE
   // Receive instruction from IMem; handle exception if any, else execute it;

   //(* no_implicit_conditions *)
   rule rl_exec(fifo_d2e.notEmpty && fifo_e2w.notFull);
      let decoded = fifo_d2e.first;

      // ----------------------------------------------------------------
      // Instruction fields decode
      Decoded_Fields fields   = fv_decode_fields(decoded.instr);

      // Calculate dependency register
      let score1 = False;
      let score2 = False;

      if (decoded.op.rs1 matches tagged Valid .x &&& x != 0)
         score1 = rg_scoreGPRs[x];

      if (decoded.op.rs2 matches tagged Valid .x &&& x != 0)
         score2 = rg_scoreGPRs[x];

      let score_conflict = score1 || score2;

      if (cfg_verbose > 2) begin
         $write("[%7d] rl_exec  : Scoreboard = [", csr_cycle);
         for(Integer i = 0; i < numRegs; i = i + 1) begin
            $write("%1d, ", rg_scoreGPRs[i] ? 1 : 0);
         end
         $write("], dst = %d, rs1 = %d, rs2 = %d\n", fields.rd, fields.rs1, fields.rs2);
      end

      if (decoded.pc != pcEpoch) begin
         if (cfg_verbose > 1) $display("[%7d] rl_exec  : STALL Ignore pc = 0x%08h, instr = 0x%08h, epoch = 0x%08h", csr_cycle, decoded.pc, decoded.instr, pcEpoch);
         fifo_d2e.deq;
      end
      else if (score_conflict) begin
         if (cfg_verbose > 1) $display("[%7d] rl_exec  : STALL Conflict pc = 0x%08h, instr = 0x%h, epoch = 0x%08h", csr_cycle, decoded.pc, decoded.instr, pcEpoch);
      end
      else begin
         if (cfg_verbose > 1) $display("[%7d] rl_exec  : pc = 0x%08h, instr = 0x%08h, epoch = 0x%08h", csr_cycle, decoded.pc, decoded.instr, pcEpoch);

         Bool checkScore = case(decoded.op.opcode) matches
                              tagged Br .*   : False;
                              tagged St .*   : False;
                              default        : True;
                           endcase;

         // Update dependency flag for $rd
         if (checkScore) begin
            rw_scoreGPRsSet.wset(fields.rd);
         end

         fa_exec(decoded, fields);
         fifo_d2e.deq;

         // Update pcEpoch
         pcEpoch <= fromMaybe(fv_fall_through_pc(pcEpoch), rw_jmpPC.wget);

         // ---------------- FINISH: increment csr_instret or record explicit CSRRx update of csr_instret
         csr_instret <= csr_instret + 1;
      end
   endrule

   // ---------------- RegFile & DMem Write Back
   rule rl_write_back(fifo_e2w.notEmpty);
      let x = fifo_e2w.first;
      fifo_e2w.deq;
      let rd = x.rd;
      let rd_value = ?;

      case(x.rd_value) matches
         tagged MemOp .op: begin
            let funct3 = op.funct3;
            let align = op.align;
            let resp <- memory.dmem_resp;

            if (cfg_verbose > 0 && !isValid(resp)) begin
               $display("[%7d] rl_write : Memory read failed", csr_cycle);
               $finish;
            end

            let u = fromMaybe(?, resp);
            let data = u >> {align, 3'b0};
            let extendFunc = (funct3 == f3_LBU || funct3 == f3_LHU) ? zeroExtend : signExtend;
            rd_value = (case (funct3)
                    f3_LB, f3_LBU: extendFunc(data[7:0]);
                    f3_LH, f3_LHU: extendFunc(data[15:0]);
                    default: extendFunc(data[31:0]);
                  endcase);
         end
         tagged Value .value: begin
            rd_value = value;
         end
      endcase

      if (cfg_verbose > 1) $display("[%7d] rl_write : %s = %h, clear scoreGPRs[%1d] (= %1d)", csr_cycle, regNameABI[rd], rd_value, rd, rg_scoreGPRs[rd] ? 1 : 0);

      // NOTE: DOES NOT check register x0 because set value to Zero when read
      rf_GPRs.upd(rd, rd_value);
      rw_scoreGPRsReset.wset(rd);
   endrule

   // ---------------- Increment csr_cycle according to external oracles

   rule rl_incr_cycle;
      csr_cycle <= csr_cycle + 1;
   endrule

   // ----------------------------------------------------------------
   // INTERFACE

   method Action start(Addr initial_pc) if (!cpu_enabled);
      pc          <= initial_pc;
      pcEpoch     <= initial_pc;
      cpu_enabled <= True;
   endmethod

   method ActionValue#(Word) cpuToHost() if (csr_dcsr matches tagged Valid .ret);
      return ret;
   endmethod
endmodule

// ================================================================

(* synthesize *)
module mkRISCV(RISCV_IFC);
   (* hide *) let _m <- _mkRISCV(0);
   return _m;
endmodule

// ================================================================
`ifdef TEST_BENCH_RISCV
module mkTb();
   Reg#(Bit#(32))    cycles <- mkConfigReg(0);

   let               dut         <- mkRISCV;
   Reg#(Bit#(16))    csr_int_low <- mkRegU;

   rule do_cycle;
      cycles <= cycles + 1;
      //if (cycles > 1200)
      //   $finish;
   endrule

   rule do_cpuToHost;
      let csr_value <- dut.cpuToHost;
      Bit#(16) csrCmd = truncateLSB(csr_value);
      Bit#(16) csrDat = truncate(csr_value);

      case(csrCmd)
         0: begin // Exit
            if (csrDat == 0) begin
               $fdisplay(stderr, "PASSED\n");
            end
            else begin
               $fdisplay(stderr, "FAILED: exit code = %d\n", csrDat);
            end
            $finish;
         end
         1: begin // PrintChar
            $fwrite(stderr, "%c", csrDat[7:0]);
         end
         2: begin // PrintIntLow
            csr_int_low <= csrDat;
         end
         3: begin // PrintIntHigh
            $fwrite(stderr, "%d", {csrDat, csr_int_low});
         end
         default: begin
            $fdisplay(stderr, "Unknown type %d", csrCmd);
            $finish;
         end
      endcase
   endrule

   rule do_start(cycles == 0);
      dut.start('h200);
   endrule
endmodule
`endif // TESTBENCH


clean up

clean up

// Copyright (c) 2016 Cornell University.

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// NOTE:
// Implement a store-and-forward mechanism between ring-buffer and
// main packet memory. Packets are buffered completely in ring buffer
// before it is sent to main memory and vice versa.

import BuildVector::*;
import Connectable::*;
import Clocks::*;
import Cntrs::*;
import ConfigReg::*;
import ClientServer::*;
import DefaultValue::*;
import DbgDefs::*;
import Ethernet::*;
import EthMac::*;
import FIFO::*;
import FIFOF::*;
import GetPut::*;
import Gearbox::*;
import MemTypes::*;
import MemServer::*;
import MemServerInternal::*;
import MemMgmt::*;
import PacketBuffer::*;
import SharedBuff::*;
import SharedBuffMMU::*;
import SpecialFIFOs::*;
import Stream::*;
import StreamGearbox::*;
import Vector::*;
import Pipe::*;
import PrintTrace::*;
import ConnectalConfig::*;
 `include "ConnectalProjectConfig.bsv"

interface StoreAndFwdFromRingToMem;
   interface PktReadClient#(16) readClient;
   interface MemAllocClient malloc;
   interface MemWriteClient#(DataBusWidth) writeClient;
   interface PipeOut#(PacketInstance) eventPktCommitted;
endinterface

module mkStoreAndFwdFromRingToMem(StoreAndFwdFromRingToMem)
   provisos (Div#(DataBusWidth, 8, bytesPerBeat)
            ,Log#(bytesPerBeat, beatShift));

   let verbose = False;

   // RingBuffer Read Client
   FIFO#(ByteStream#(16)) readDataFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) readLenFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) readReqFifo <- mkFIFO;

   // Memory Client
   FIFO#(MemRequest) writeReqFifo <- mkSizedFIFO(4);
   FIFO#(MemData#(DataBusWidth)) writeDataFifo <- mkSizedFIFO(16);
   FIFO#(Bit#(MemTagSize)) writeDoneFifo <- mkSizedFIFO(4);
   MemWriteClient#(DataBusWidth) dmaWriteClient = (interface MemWriteClient;
   interface Get writeReq = toGet(writeReqFifo);
   interface Get writeData = toGet(writeDataFifo);
   interface Put writeDone = toPut(writeDoneFifo);
   endinterface);

   FIFO#(Bit#(EtherLen)) mallocReqFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) pktLenFifo <- mkFIFO;
   FIFO#(Maybe#(PktId)) mallocDoneFifo <- mkFIFO;
   Reg#(Bool) readStarted <- mkReg(False);
   Reg#(Bool) mallocd <- mkReg(False);

   FIFOF#(PacketInstance) eventPktReceivedFifo <- mkFIFOF;
   FIFOF#(PacketInstance) eventPktCommittedFifo <- mkFIFOF;

   Reg#(Bit#(32)) cycle <- mkReg(0);
   rule every1 if (verbose);
      cycle <= cycle + 1;
   endrule

   rule packetReadStart if (!readStarted);
      let pktLen <- toGet(readLenFifo).get;
      if (verbose) $display("StoreAndForward::packetReadStart %d: ReadLen %d", cycle, pktLen);
      mallocReqFifo.enq(pktLen);
      pktLenFifo.enq(pktLen);
      readStarted <= True;
   endrule

   rule allocMemory;
      let pktLen <- toGet(pktLenFifo).get;
      let allocId <- toGet(mallocDoneFifo).get;
      let bytesPerBeatMinusOne = fromInteger(valueOf(bytesPerBeat))-1;
      // roundup to 16 byte boundary
      let burstLen = ((pktLen + bytesPerBeatMinusOne) & ~(bytesPerBeatMinusOne));
      let mask = (1<< (pktLen % fromInteger(valueOf(bytesPerBeat))))-1;
      if (isValid(allocId)) begin
         mallocd <= True;
         readReqFifo.enq(truncate(pktLen));
         //FIXME use correct sglId
         writeReqFifo.enq(MemRequest {sglId: extend(fromMaybe(?, allocId)), offset: 0,
                                      burstLen: truncate(burstLen), tag:0
`ifdef BYTE_ENABLES
                                      , firstbe: 'hffff, lastbe: mask
`endif
                                     });
         if (verbose) $display("StoreAndForward::allocMemory %d: alloc done", cycle);
         eventPktReceivedFifo.enq(PacketInstance {id: fromMaybe(?, allocId), size: pktLen});
      end
   endrule

   rule packetReadInProgress if (readStarted && mallocd);
      let v <- toGet(readDataFifo).get;
      if (v.eop) begin
         readStarted <= False;
         mallocd <= False;
         if (verbose) $display("StoreAndForward:: %d: packet finished", cycle);
      end
      if (verbose) $display("StoreAndForward::writeData: %d: data:%h, tag:%h, last:%h", cycle, v.data, 0, v.eop);
      writeDataFifo.enq(MemData {data: v.data, tag: 0, last: v.eop});
   endrule

   rule packetReadDone;
      let v <- toGet(writeDoneFifo).get;
      let recvd <- toGet(eventPktReceivedFifo).get;
      eventPktCommittedFifo.enq(recvd);
      if (verbose) $display("StoreAndForward::packetReadDone %d: packet written to memory %h", cycle, v);
   endrule

   interface PktReadClient readClient;
      interface readData = toPut(readDataFifo);
      interface readLen = toPut(readLenFifo);
      interface readReq = toGet(readReqFifo);
   endinterface

   interface malloc = (interface MemAllocClient;
      interface Get mallocReq = toGet(mallocReqFifo);
      interface Put mallocDone = toPut(mallocDoneFifo);
   endinterface);
   interface writeClient = dmaWriteClient;
   interface PipeOut eventPktCommitted = toPipeOut(eventPktCommittedFifo);
endmodule

interface StoreAndFwdFromMemToRing;
   interface PktWriteClient#(16) writeClient;
   interface MemReadClient#(DataBusWidth) readClient;
   interface PipeIn#(PacketInstance) eventPktSend;
   interface MemFreeClient free;
endinterface

module mkStoreAndFwdFromMemToRing(StoreAndFwdFromMemToRing)
   provisos (Div#(DataBusWidth, 8, bytesPerBeat)
            ,Log#(bytesPerBeat, beatShift));

   Reg#(int) cf_verbosity <- mkConfigReg(4);
   function Action dbprint(Integer level, Fmt msg);
      action
      if (cf_verbosity > fromInteger(level)) begin
        $display("(%0d) ", $time, msg);
      end
      endaction
   endfunction

   // Ring Buffer Write Client
   FIFO#(ByteStream#(16)) writeDataFifo <- mkFIFO;

   // read client interface
   FIFO#(MemRequest) readReqFifo <-mkSizedFIFO(4);
   FIFO#(MemData#(DataBusWidth)) readDataFifo <- mkSizedFIFO(32);
   MemReadClient#(DataBusWidth) dmaReadClient = (interface MemReadClient;
   interface Get readReq = toGet(readReqFifo);
   interface Put readData = toPut(readDataFifo);
   endinterface);

   FIFOF#(PacketInstance) eventPktSendFifo <- mkSizedFIFOF(16);
   FIFOF#(Bit#(EtherLen)) readBurstLenFifo <- mkSizedFIFOF(16);
   FIFOF#(PktId) currPacketIdFifo <- mkSizedFIFOF(16);
   FIFO#(PktId) freeReqFifo <- mkSizedFIFO(4);

   Reg#(Bool)                 outPacket <- mkReg(False);
   Reg#(Bit#(EtherLen))   readBurstCount <- mkReg(maxBound);
   Reg#(Bool)                 started <- mkReg(False);

   rule packetReadStart;
      let pkt <- toGet(eventPktSendFifo).get;
      let bytesPerBeatMinusOne = fromInteger(valueOf(bytesPerBeat))-1;
      // roundup to 16 byte boundary
      let burstLen = ((pkt.size + bytesPerBeatMinusOne) & ~(bytesPerBeatMinusOne));
      dbprint(3, $format("StoreAndForward:packetReadStart %h, burstLen = %h", pkt.size, burstLen));
      let mask = (1<< (pkt.size % fromInteger(valueOf(bytesPerBeat))))-1;
      readReqFifo.enq(MemRequest{sglId: extend(pkt.id), offset: 0,
                                 burstLen: truncate(burstLen), tag: 0
`ifdef BYTE_ENABLES
                                 , firstbe: 'hffff, lastbe: mask
`endif
                                });
      dbprint(3, $format("StoreAndForward:packetReadStart send a new packet with size %h %h", burstLen, pack(mask)));
      currPacketIdFifo.enq(pkt.id);
      readBurstLenFifo.enq(pkt.size);
   endrule

   rule packetReadInProgress if (readBurstLenFifo.notEmpty);
      let d <- toGet(readDataFifo).get;
      let _bytesPerBeat= fromInteger(valueOf(bytesPerBeat));

      let readBurstLen = readBurstLenFifo.first;
      let currPacketId = currPacketIdFifo.first;

      let sop = False;
      let eop = False;
      if (!started) begin
         readBurstCount <= readBurstLen - _bytesPerBeat;
         started <= True;
         sop = True;
      end
      else if (readBurstCount <= _bytesPerBeat && started) begin
         readBurstLenFifo.deq;
         currPacketIdFifo.deq;
         readBurstCount <= maxBound;
         started <= False;
         eop = True;
         freeReqFifo.enq(currPacketId);
      end
      else if (readBurstCount > _bytesPerBeat && started) begin
         readBurstCount <= readBurstCount - _bytesPerBeat;
      end

      Bit#(bytesPerBeat) mask;
      if (readBurstCount <= _bytesPerBeat)
         mask = (1<<readBurstCount)-1;
      else
         mask = (1<<_bytesPerBeat)-1;

      ByteStream#(16) v = defaultValue;
      v.data = d.data;
      v.mask = mask;
      v.sop = sop;
      v.eop = eop;
      writeDataFifo.enq(v);

      dbprint(3, $format("StoreAndForward:readdata  %h %h %h %h %h", readBurstCount, d.data, pack(mask), sop, eop));
   endrule

   interface PktWriteClient writeClient;
      interface writeData = toGet(writeDataFifo);
   endinterface
   interface readClient = dmaReadClient;
   interface PipeIn eventPktSend = toPipeIn(eventPktSendFifo);
   interface free = (interface MemFreeClient;
      interface Get freeReq = toGet(freeReqFifo);
   endinterface);
endmodule

interface StoreAndFwdFromRingToMac;
   interface PktReadClient#(16) readClient;
   interface Get#(ByteStream#(8)) macTx;
   method TxThruDbgRec dbg;
   method ThruDbgRec sdbg;
endinterface

// store data from ring buffer to network
// big-endianess -> little-endianess
module mkStoreAndFwdFromRingToMac#(Clock txClock, Reset txReset)(StoreAndFwdFromRingToMac);
   let verbose = False;
   Clock defaultClock <- exposeCurrentClock();
   Reset defaultReset <- exposeCurrentReset();

   Reg#(Bit#(64)) cycle_cnt <- mkReg(0);
   Reg#(Bit#(64)) last_startofpacket <- mkReg(0);
   Reg#(Bit#(64)) last_endofpacket <- mkReg(0);
   Reg#(Bit#(64)) goodputCount <- mkReg(0);
   Reg#(Bit#(64)) idleCount <- mkReg(0);

   // stats
   Reg#(Bit#(64)) total_cycles <- mkReg(0, clocked_by txClock, reset_by txReset);
   Reg#(Bit#(64)) idle_cycles <- mkReg(0, clocked_by txClock, reset_by txReset);
   Reg#(Bit#(64)) sopCount <- mkReg(0, clocked_by txClock, reset_by txReset);
   Reg#(Bit#(64)) eopCount <- mkReg(0, clocked_by txClock, reset_by txReset);
   Reg#(Bit#(64)) data_bytes <- mkReg(0, clocked_by txClock, reset_by txReset);

   // RingBuffer Read Client
   FIFO#(ByteStream#(16)) readDataFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) readLenFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) readReqFifo <- mkFIFO;

   // Mac Facing Fifo
   FIFOF#(ByteStream#(8)) writeMacFifo <- mkFIFOF(clocked_by txClock, reset_by txReset);
   Gearbox#(2, 1, ByteStream#(8)) fifoTxData <- mkNto1Gearbox(txClock, txReset, txClock, txReset);
   SyncFIFOIfc#(ByteStream#(16)) tx_fifo <- mkSyncFIFO(5, defaultClock, defaultReset, txClock);

   rule cycle;
      cycle_cnt <= cycle_cnt + 1;
   endrule

   rule total_cycle;
      total_cycles <= total_cycles + 1;
   endrule

   rule readDataStart;
      let pktLen <- toGet(readLenFifo).get;
      if (verbose) $display(fshow(" read packt ") + fshow(pktLen));
      readReqFifo.enq(pktLen);
   endrule

   function Vector#(2, ByteStream#(8)) split(ByteStream#(16) in);
      Vector#(2, ByteStream#(8)) v = defaultValue;
      Vector#(8, Bit#(8)) v0_data = unpack(in.data[63:0]);
      Vector#(8, Bit#(8)) v1_data = unpack(in.data[127:64]);
      v[0].sop = in.sop;
`ifdef ALTERA
      v[0].data = pack(reverse(v0_data));
`else
      v[0].data = pack(v0_data);
`endif
      v[0].eop = (in.mask[15:8] == 0) ? in.eop : False;
      v[0].mask = in.mask[7:0];
      v[1].sop = False;
`ifdef ALTERA
      v[1].data = pack(reverse(v1_data));
`else
      v[1].data = pack(v1_data);
`endif
      v[1].eop = in.eop;
      v[1].mask = in.mask[15:8];
      return v;
   endfunction

   rule cross_clocking;
      let v <- toGet(readDataFifo).get;
      tx_fifo.enq(v);

      // performance analysis
      if (v.sop) begin
         last_startofpacket <= cycle_cnt;
         idleCount <= (last_endofpacket != 0) ? (idleCount + (cycle_cnt - last_endofpacket)) : 0;
      end
      if (v.eop) begin
         last_endofpacket <= cycle_cnt;
         goodputCount <= (last_startofpacket != 0) ? (goodputCount + (cycle_cnt - last_startofpacket)) : 0;
      end
   endrule

   rule process_incoming_packet;
      let v <- toGet(tx_fifo).get;
      fifoTxData.enq(split(v));
   endrule

   rule process_outgoing_packet;
      let data = fifoTxData.first; fifoTxData.deq;
      let temp = head(data);
      let bytes = zeroExtend(pack(countOnes(temp.mask)));
      if (temp.sop) sopCount <= sopCount + 1;
      data_bytes <= data_bytes + bytes;
      if (temp.mask != 0) begin
         if (verbose) $display("ringToMac:: tx data %h", temp.data);
         if (temp.eop) eopCount <= eopCount + 1;
         writeMacFifo.enq(temp);
      end
      else begin
         idle_cycles <= idle_cycles + 1;
      end
   endrule

   rule count_idle_cycles (!fifoTxData.notEmpty);
      idle_cycles <= idle_cycles + 1;
   endrule

   interface PktReadClient readClient;
      interface readData = toPut(readDataFifo);
      interface readLen = toPut(readLenFifo);
      interface readReq = toGet(readReqFifo);
   endinterface
   interface Get macTx = toGet(writeMacFifo);
   method TxThruDbgRec dbg;
      return TxThruDbgRec {goodputCount: goodputCount, idleCount: idleCount};
   endmethod
   method ThruDbgRec sdbg;
      return ThruDbgRec {data_bytes: data_bytes, sops: sopCount, eops: eopCount, idle_cycles: idle_cycles, total_cycles: total_cycles};
   endmethod
endmodule

interface StoreAndFwdFromMacToRing;
   interface PktWriteClient#(16) writeClient;
   interface Put#(ByteStream#(8)) macRx;
   method ThruDbgRec sdbg;
endinterface

// store data from network to ring buffer
// little-endianess -> big-endianess
module mkStoreAndFwdFromMacToRing#(Clock rxClock, Reset rxReset)(StoreAndFwdFromMacToRing);
   let verbose = True;
   Clock defaultClock <- exposeCurrentClock();
   Reset defaultReset <- exposeCurrentReset();

   // stats
   Reg#(Bit#(64)) total_cycles <- mkReg(0, clocked_by rxClock, reset_by rxReset);
   Reg#(Bit#(64)) idle_cycles <- mkReg(0, clocked_by rxClock, reset_by rxReset);
   Reg#(Bit#(64)) sopCount <- mkReg(0, clocked_by rxClock, reset_by rxReset);
   Reg#(Bit#(64)) eopCount <- mkReg(0, clocked_by rxClock, reset_by rxReset);
   Reg#(Bit#(64)) data_bytes <- mkReg(0, clocked_by rxClock, reset_by rxReset);

   rule total_cycle;
      total_cycles <= total_cycles + 1;
   endrule

   StreamGearbox#(8, 16) gearbox <- mkStreamGearboxUp(clocked_by rxClock, reset_by rxReset);
   SyncFIFOIfc#(ByteStream#(16)) writeDataFifo <- mkSyncFIFO(5, rxClock, rxReset, defaultClock);

   rule writeData;
      let v <- gearbox.dataout.get;
      writeDataFifo.enq(v);
      if (verbose) $display("(%0d) macToRing:: writeToFifo", $time);
   endrule

//   rule count_idle_cycles (!inProgress);
//      idle_cycles <= idle_cycles + 1;
//   endrule

   interface PktWriteClient writeClient;
      interface writeData = toGet(writeDataFifo);
   endinterface
   interface macRx = gearbox.datain;
   method ThruDbgRec sdbg;
      return ThruDbgRec {data_bytes: data_bytes, sops: sopCount, eops: eopCount, idle_cycles: idle_cycles, total_cycles: total_cycles};
   endmethod
endmodule

// Copyright (c) 2016 Cornell University.

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// NOTE:
// Implement a store-and-forward mechanism between ring-buffer and
// main packet memory. Packets are buffered completely in ring buffer
// before it is sent to main memory and vice versa.

import BuildVector::*;
import Connectable::*;
import Clocks::*;
import Cntrs::*;
import ConfigReg::*;
import ClientServer::*;
import DefaultValue::*;
import DbgDefs::*;
import Ethernet::*;
import EthMac::*;
import FIFO::*;
import FIFOF::*;
import GetPut::*;
import Gearbox::*;
import MemTypes::*;
import MemServer::*;
import MemServerInternal::*;
import MemMgmt::*;
import PacketBuffer::*;
import SharedBuff::*;
import SharedBuffMMU::*;
import SpecialFIFOs::*;
import Stream::*;
import StreamGearbox::*;
import Vector::*;
import Pipe::*;
import PrintTrace::*;
import ConnectalConfig::*;
 `include "ConnectalProjectConfig.bsv"

interface StoreAndFwdFromRingToMem;
   interface PktReadClient#(16) readClient;
   interface MemAllocClient malloc;
   interface MemWriteClient#(DataBusWidth) writeClient;
   interface PipeOut#(PacketInstance) eventPktCommitted;
endinterface

module mkStoreAndFwdFromRingToMem(StoreAndFwdFromRingToMem)
   provisos (Div#(DataBusWidth, 8, bytesPerBeat)
            ,Log#(bytesPerBeat, beatShift));

   let verbose = False;

   // RingBuffer Read Client
   FIFO#(ByteStream#(16)) readDataFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) readLenFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) readReqFifo <- mkFIFO;

   // Memory Client
   FIFO#(MemRequest) writeReqFifo <- mkSizedFIFO(4);
   FIFO#(MemData#(DataBusWidth)) writeDataFifo <- mkSizedFIFO(16);
   FIFO#(Bit#(MemTagSize)) writeDoneFifo <- mkSizedFIFO(4);
   MemWriteClient#(DataBusWidth) dmaWriteClient = (interface MemWriteClient;
   interface Get writeReq = toGet(writeReqFifo);
   interface Get writeData = toGet(writeDataFifo);
   interface Put writeDone = toPut(writeDoneFifo);
   endinterface);

   FIFO#(Bit#(EtherLen)) mallocReqFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) pktLenFifo <- mkFIFO;
   FIFO#(Maybe#(PktId)) mallocDoneFifo <- mkFIFO;
   Reg#(Bool) readStarted <- mkReg(False);
   Reg#(Bool) mallocd <- mkReg(False);

   FIFOF#(PacketInstance) eventPktReceivedFifo <- mkFIFOF;
   FIFOF#(PacketInstance) eventPktCommittedFifo <- mkFIFOF;

   Reg#(Bit#(32)) cycle <- mkReg(0);
   rule every1 if (verbose);
      cycle <= cycle + 1;
   endrule

   rule packetReadStart if (!readStarted);
      let pktLen <- toGet(readLenFifo).get;
      if (verbose) $display("StoreAndForward::packetReadStart %d: ReadLen %d", cycle, pktLen);
      mallocReqFifo.enq(pktLen);
      pktLenFifo.enq(pktLen);
      readStarted <= True;
   endrule

   rule allocMemory;
      let pktLen <- toGet(pktLenFifo).get;
      let allocId <- toGet(mallocDoneFifo).get;
      let bytesPerBeatMinusOne = fromInteger(valueOf(bytesPerBeat))-1;
      // roundup to 16 byte boundary
      let burstLen = ((pktLen + bytesPerBeatMinusOne) & ~(bytesPerBeatMinusOne));
      let mask = (1<< (pktLen % fromInteger(valueOf(bytesPerBeat))))-1;
      if (isValid(allocId)) begin
         mallocd <= True;
         readReqFifo.enq(truncate(pktLen));
         //FIXME use correct sglId
         writeReqFifo.enq(MemRequest {sglId: extend(fromMaybe(?, allocId)), offset: 0,
                                      burstLen: truncate(burstLen), tag:0
`ifdef BYTE_ENABLES
                                      , firstbe: 'hffff, lastbe: mask
`endif
                                     });
         if (verbose) $display("StoreAndForward::allocMemory %d: alloc done", cycle);
         eventPktReceivedFifo.enq(PacketInstance {id: fromMaybe(?, allocId), size: pktLen});
      end
   endrule

   rule packetReadInProgress if (readStarted && mallocd);
      let v <- toGet(readDataFifo).get;
      if (v.eop) begin
         readStarted <= False;
         mallocd <= False;
         if (verbose) $display("StoreAndForward:: %d: packet finished", cycle);
      end
      if (verbose) $display("StoreAndForward::writeData: %d: data:%h, tag:%h, last:%h", cycle, v.data, 0, v.eop);
      writeDataFifo.enq(MemData {data: v.data, tag: 0, last: v.eop});
   endrule

   rule packetReadDone;
      let v <- toGet(writeDoneFifo).get;
      let recvd <- toGet(eventPktReceivedFifo).get;
      eventPktCommittedFifo.enq(recvd);
      if (verbose) $display("StoreAndForward::packetReadDone %d: packet written to memory %h", cycle, v);
   endrule

   interface PktReadClient readClient;
      interface readData = toPut(readDataFifo);
      interface readLen = toPut(readLenFifo);
      interface readReq = toGet(readReqFifo);
   endinterface

   interface malloc = (interface MemAllocClient;
      interface Get mallocReq = toGet(mallocReqFifo);
      interface Put mallocDone = toPut(mallocDoneFifo);
   endinterface);
   interface writeClient = dmaWriteClient;
   interface PipeOut eventPktCommitted = toPipeOut(eventPktCommittedFifo);
endmodule

interface StoreAndFwdFromMemToRing;
   interface PktWriteClient#(16) writeClient;
   interface MemReadClient#(DataBusWidth) readClient;
   interface PipeIn#(PacketInstance) eventPktSend;
   interface MemFreeClient free;
endinterface

module mkStoreAndFwdFromMemToRing(StoreAndFwdFromMemToRing)
   provisos (Div#(DataBusWidth, 8, bytesPerBeat)
            ,Log#(bytesPerBeat, beatShift));

   Reg#(int) cf_verbosity <- mkConfigReg(4);
   function Action dbprint(Integer level, Fmt msg);
      action
      if (cf_verbosity > fromInteger(level)) begin
        $display("(%0d) ", $time, msg);
      end
      endaction
   endfunction

   // Ring Buffer Write Client
   FIFO#(ByteStream#(16)) writeDataFifo <- mkFIFO;

   // read client interface
   FIFO#(MemRequest) readReqFifo <-mkSizedFIFO(4);
   FIFO#(MemData#(DataBusWidth)) readDataFifo <- mkSizedFIFO(32);
   MemReadClient#(DataBusWidth) dmaReadClient = (interface MemReadClient;
   interface Get readReq = toGet(readReqFifo);
   interface Put readData = toPut(readDataFifo);
   endinterface);

   FIFOF#(PacketInstance) eventPktSendFifo <- mkSizedFIFOF(16);
   FIFOF#(Bit#(EtherLen)) readBurstLenFifo <- mkSizedFIFOF(16);
   FIFOF#(PktId) currPacketIdFifo <- mkSizedFIFOF(16);
   FIFO#(PktId) freeReqFifo <- mkSizedFIFO(4);

   Reg#(Bool)                 outPacket <- mkReg(False);
   Reg#(Bit#(EtherLen))   readBurstCount <- mkReg(maxBound);
   Reg#(Bool)                 started <- mkReg(False);

   rule packetReadStart;
      let pkt <- toGet(eventPktSendFifo).get;
      let bytesPerBeatMinusOne = fromInteger(valueOf(bytesPerBeat))-1;
      // roundup to 16 byte boundary
      let burstLen = ((pkt.size + bytesPerBeatMinusOne) & ~(bytesPerBeatMinusOne));
      dbprint(3, $format("StoreAndForward:packetReadStart %h, burstLen = %h", pkt.size, burstLen));
      let mask = (1<< (pkt.size % fromInteger(valueOf(bytesPerBeat))))-1;
      readReqFifo.enq(MemRequest{sglId: extend(pkt.id), offset: 0,
                                 burstLen: truncate(burstLen), tag: 0
`ifdef BYTE_ENABLES
                                 , firstbe: 'hffff, lastbe: mask
`endif
                                });
      dbprint(3, $format("StoreAndForward:packetReadStart send a new packet with size %h %h", burstLen, pack(mask)));
      currPacketIdFifo.enq(pkt.id);
      readBurstLenFifo.enq(pkt.size);
   endrule

   rule packetReadInProgress if (readBurstLenFifo.notEmpty);
      let d <- toGet(readDataFifo).get;
      let _bytesPerBeat= fromInteger(valueOf(bytesPerBeat));

      let readBurstLen = readBurstLenFifo.first;
      let currPacketId = currPacketIdFifo.first;

      let sop = False;
      let eop = False;
      if (!started) begin
         readBurstCount <= readBurstLen - _bytesPerBeat;
         started <= True;
         sop = True;
      end
      else if (readBurstCount <= _bytesPerBeat && started) begin
         readBurstLenFifo.deq;
         currPacketIdFifo.deq;
         readBurstCount <= maxBound;
         started <= False;
         eop = True;
         freeReqFifo.enq(currPacketId);
      end
      else if (readBurstCount > _bytesPerBeat && started) begin
         readBurstCount <= readBurstCount - _bytesPerBeat;
      end

      Bit#(bytesPerBeat) mask;
      if (readBurstCount <= _bytesPerBeat)
         mask = (1<<readBurstCount)-1;
      else
         mask = (1<<_bytesPerBeat)-1;

      ByteStream#(16) v = defaultValue;
      v.data = d.data;
      v.mask = mask;
      v.sop = sop;
      v.eop = eop;
      writeDataFifo.enq(v);

      dbprint(3, $format("StoreAndForward:readdata  %h %h %h %h %h", readBurstCount, d.data, pack(mask), sop, eop));
   endrule

   interface PktWriteClient writeClient;
      interface writeData = toGet(writeDataFifo);
   endinterface
   interface readClient = dmaReadClient;
   interface PipeIn eventPktSend = toPipeIn(eventPktSendFifo);
   interface free = (interface MemFreeClient;
      interface Get freeReq = toGet(freeReqFifo);
   endinterface);
endmodule

interface StoreAndFwdFromRingToMac;
   interface PktReadClient#(16) readClient;
   interface Get#(ByteStream#(8)) macTx;
   method TxThruDbgRec dbg;
   method ThruDbgRec sdbg;
endinterface

// store data from ring buffer to network
// big-endianess -> little-endianess
module mkStoreAndFwdFromRingToMac#(Clock txClock, Reset txReset)(StoreAndFwdFromRingToMac);
   let verbose = False;
   Clock defaultClock <- exposeCurrentClock();
   Reset defaultReset <- exposeCurrentReset();

   Reg#(Bit#(64)) cycle_cnt <- mkReg(0);
   Reg#(Bit#(64)) last_startofpacket <- mkReg(0);
   Reg#(Bit#(64)) last_endofpacket <- mkReg(0);
   Reg#(Bit#(64)) goodputCount <- mkReg(0);
   Reg#(Bit#(64)) idleCount <- mkReg(0);

   // stats
   Reg#(Bit#(64)) total_cycles <- mkReg(0, clocked_by txClock, reset_by txReset);
   Reg#(Bit#(64)) idle_cycles <- mkReg(0, clocked_by txClock, reset_by txReset);
   Reg#(Bit#(64)) sopCount <- mkReg(0, clocked_by txClock, reset_by txReset);
   Reg#(Bit#(64)) eopCount <- mkReg(0, clocked_by txClock, reset_by txReset);
   Reg#(Bit#(64)) data_bytes <- mkReg(0, clocked_by txClock, reset_by txReset);

   // RingBuffer Read Client
   FIFO#(ByteStream#(16)) readDataFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) readLenFifo <- mkFIFO;
   FIFO#(Bit#(EtherLen)) readReqFifo <- mkFIFO;

   // Mac Facing Fifo
   FIFOF#(ByteStream#(8)) writeMacFifo <- mkFIFOF(clocked_by txClock, reset_by txReset);
   Gearbox#(2, 1, ByteStream#(8)) fifoTxData <- mkNto1Gearbox(txClock, txReset, txClock, txReset);
   SyncFIFOIfc#(ByteStream#(16)) tx_fifo <- mkSyncFIFO(5, defaultClock, defaultReset, txClock);

   rule cycle;
      cycle_cnt <= cycle_cnt + 1;
   endrule

   rule total_cycle;
      total_cycles <= total_cycles + 1;
   endrule

   rule readDataStart;
      let pktLen <- toGet(readLenFifo).get;
      if (verbose) $display(fshow(" read packt ") + fshow(pktLen));
      readReqFifo.enq(pktLen);
   endrule

   function Vector#(2, ByteStream#(8)) split(ByteStream#(16) in);
      Vector#(2, ByteStream#(8)) v = defaultValue;
      Vector#(8, Bit#(8)) v0_data = unpack(in.data[63:0]);
      Vector#(8, Bit#(8)) v1_data = unpack(in.data[127:64]);
      v[0].sop = in.sop;
`ifdef ALTERA
      v[0].data = pack(reverse(v0_data));
`else
      v[0].data = pack(v0_data);
`endif
      v[0].eop = (in.mask[15:8] == 0) ? in.eop : False;
      v[0].mask = in.mask[7:0];
      v[1].sop = False;
`ifdef ALTERA
      v[1].data = pack(reverse(v1_data));
`else
      v[1].data = pack(v1_data);
`endif
      v[1].eop = in.eop;
      v[1].mask = in.mask[15:8];
      return v;
   endfunction

   rule cross_clocking;
      let v <- toGet(readDataFifo).get;
      tx_fifo.enq(v);

      // performance analysis
      if (v.sop) begin
         last_startofpacket <= cycle_cnt;
         idleCount <= (last_endofpacket != 0) ? (idleCount + (cycle_cnt - last_endofpacket)) : 0;
      end
      if (v.eop) begin
         last_endofpacket <= cycle_cnt;
         goodputCount <= (last_startofpacket != 0) ? (goodputCount + (cycle_cnt - last_startofpacket)) : 0;
      end
   endrule

   rule process_incoming_packet;
      let v <- toGet(tx_fifo).get;
      fifoTxData.enq(split(v));
   endrule

   rule process_outgoing_packet;
      let data = fifoTxData.first; fifoTxData.deq;
      let temp = head(data);
      let bytes = zeroExtend(pack(countOnes(temp.mask)));
      if (temp.sop) sopCount <= sopCount + 1;
      data_bytes <= data_bytes + bytes;
      if (temp.mask != 0) begin
         if (verbose) $display("ringToMac:: tx data %h", temp.data);
         if (temp.eop) eopCount <= eopCount + 1;
         writeMacFifo.enq(temp);
      end
      else begin
         idle_cycles <= idle_cycles + 1;
      end
   endrule

   rule count_idle_cycles (!fifoTxData.notEmpty);
      idle_cycles <= idle_cycles + 1;
   endrule

   interface PktReadClient readClient;
      interface readData = toPut(readDataFifo);
      interface readLen = toPut(readLenFifo);
      interface readReq = toGet(readReqFifo);
   endinterface
   interface Get macTx = toGet(writeMacFifo);
   method TxThruDbgRec dbg;
      return TxThruDbgRec {goodputCount: goodputCount, idleCount: idleCount};
   endmethod
   method ThruDbgRec sdbg;
      return ThruDbgRec {data_bytes: data_bytes, sops: sopCount, eops: eopCount, idle_cycles: idle_cycles, total_cycles: total_cycles};
   endmethod
endmodule

interface StoreAndFwdFromMacToRing;
   interface PktWriteClient#(16) writeClient;
   interface Put#(ByteStream#(8)) macRx;
   method ThruDbgRec sdbg;
endinterface

// store data from network to ring buffer
// little-endianess -> big-endianess
module mkStoreAndFwdFromMacToRing#(Clock rxClock, Reset rxReset)(StoreAndFwdFromMacToRing);
   let verbose = True;
   Clock defaultClock <- exposeCurrentClock();
   Reset defaultReset <- exposeCurrentReset();

   // stats
   Reg#(Bit#(64)) total_cycles <- mkReg(0, clocked_by rxClock, reset_by rxReset);
   Reg#(Bit#(64)) idle_cycles <- mkReg(0, clocked_by rxClock, reset_by rxReset);
   Reg#(Bit#(64)) sopCount <- mkReg(0, clocked_by rxClock, reset_by rxReset);
   Reg#(Bit#(64)) eopCount <- mkReg(0, clocked_by rxClock, reset_by rxReset);
   Reg#(Bit#(64)) data_bytes <- mkReg(0, clocked_by rxClock, reset_by rxReset);

   rule total_cycle;
      total_cycles <= total_cycles + 1;
   endrule

   StreamGearbox#(8, 16) gearbox <- mkStreamGearboxUp(clocked_by rxClock, reset_by rxReset);
   SyncFIFOIfc#(ByteStream#(16)) writeDataFifo <- mkSyncFIFO(5, rxClock, rxReset, defaultClock);

   rule writeData;
      let v <- gearbox.dataout.get;
      writeDataFifo.enq(v);
      if (verbose) $display("macToRing:: writeToFifo");
   endrule

//   rule count_idle_cycles (!inProgress);
//      idle_cycles <= idle_cycles + 1;
//   endrule

   interface PktWriteClient writeClient;
      interface writeData = toGet(writeDataFifo);
   endinterface
   interface macRx = gearbox.datain;
   method ThruDbgRec sdbg;
      return ThruDbgRec {data_bytes: data_bytes, sops: sopCount, eops: eopCount, idle_cycles: idle_cycles, total_cycles: total_cycles};
   endmethod
endmodule