single multi-cycle core boots linux

single multi-cycle core boots linux

import BRAMCore::*;
import Vector::*;
import Fifo::*;
import Types::*;
import ProcTypes::*;
import MMIOAddrs::*;
import MMIOCore::*;
import CacheUtils::*;
import Amo::*;

// MMIO logic at platform (MMIOPlatform)
// XXX Currently all MMIO requests and posts of timer interrupts are handled
// one by one in a blocking manner. This is extremely conservative. Hopefully
// this may help avoid some kernel-level problems.

interface MMIOPlatform;
    method Action bootRomInitReq(BootRomIndex index, Data data);
    method ActionValue#(void) bootRomInitResp;
    method Action start(Addr toHost, Addr fromHost);
    method ActionValue#(Data) to_host;
    method Action from_host(Data x);
endinterface

typedef enum {
    Init,
    SelectReq,
    ProcessReq,
    WaitResp
} MMIOPlatformState deriving(Bits, Eq, FShow);

// MMIO device/reg targed by the core request together with offset within
// reg/device
typedef union tagged {
    void Invalid; // invalid req target
    void TimerInterrupt; // auto-generated timer interrupt
    BootRomIndex BootRom;
    MSIPDataAlignedOffset MSIP;
    MTimCmpDataAlignedOffset MTimeCmp;
    void MTime;
    void ToHost;
    void FromHost;
} MMIOPlatformReq deriving(Bits, Eq, FShow);

module mkMMIOPlatform#(Vector#(CoreNum, MMIOCoreToPlatform) cores)(
    MMIOPlatform
) provisos(
    Bits#(Data, 64) // this module assumes Data is 64-bit wide
);
    Bool verbose = True;

    // boot rom
    BRAM_PORT_BE#(BootRomIndex, Data, NumBytes) bootRom <- mkBRAMCore1BE(
        valueOf(TExp#(LgBootRomSzData)), False
    );
    // mtimecmp
    Vector#(CoreNum, Reg#(Data)) mtimecmp <- replicateM(mkReg(0));
    // mtime
    Reg#(Data) mtime <- mkReg(0);
    // HTIF mem mapped addrs
    Fifo#(1, Data) toHostQ <- mkCFFifo;
    Fifo#(1, Data) fromHostQ <- mkCFFifo;
    Reg#(DataAlignedAddr) toHostAddr <- mkReg(0);
    Reg#(DataAlignedAddr) fromHostAddr <- mkReg(0);

    // state machine
    Reg#(MMIOPlatformState) state <- mkReg(Init);
    // current req (valid when state != Init && state != SelectReq
    Reg#(MMIOPlatformReq) curReq <- mkRegU;
    Reg#(CoreId) reqCore <- mkRegU;
    Reg#(MMIOFunc) reqFunc <- mkRegU;
    Reg#(ByteEn) reqBE <- mkRegU;
    Reg#(Data) reqData <- mkRegU;
    // we need to wait for resp from cores when we need to change MTIP
    Reg#(Vector#(CoreNum, Bool)) waitMTIPCRs <- mkRegU;
    // for MSIP access: lower bits and upper bits of requested memory location
    // correspond to two cores. We need to wait resp from these two cores.
    Reg#(Maybe#(CoreId)) waitLowerMSIPCRs <- mkRegU;
    Reg#(Maybe#(CoreId)) waitUpperMSIPCRs <- mkRegU;
    // in case of AMO on mtime and mtimecmp, resp may be sent after waiting for
    // CRs, we record the AMO resp at processing time
    Reg#(Data) amoResp <- mkRegU;

    // we increment mtime periodically
    Reg#(Bit#(TLog#(CyclesPerTimeInc))) cycle <- mkReg(0);

    // To avoid posting timer interrupt repeatedly, we keep a copy of MTIP
    // here. Since each core cannot write MTIP by CSRXXX inst, the only way to
    // change MTIP is through here.
    Vector#(CoreNum, Reg#(Bool)) mtip <- replicateM(mkReg(False));

    // respQ for boot rom init
    Fifo#(1, void) bootRomInitRespQ <- mkCFFifo;

    // pass mtime to each core
    rule propagateTime(state != Init);
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            cores[i].setTime(mtime);
        end
    endrule

    rule incCycle(
        state != Init &&
        cycle < fromInteger(valueof(CyclesPerTimeInc) - 1)
    );
        cycle <= cycle + 1;
    endrule

    // we don't increment mtime when processing a req
    rule incTime(
        state == SelectReq &&
        cycle >= fromInteger(valueof(CyclesPerTimeInc) - 1)
    );
        cycle <= 0;
        mtime <= mtime + fromInteger(valueof(TicksPerTimeInc));
    endrule

    // since we only process 1 MMIO req or timer interrupt at a time, we can
    // enq/deq all FIFOs in one rule

    (* preempts = "incTime, selectReq" *)
    rule selectReq(state == SelectReq);
        // check for timer interrupt
        Vector#(CoreNum, Bool) needTimerInt = replicate(False);
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            if(!mtip[i] && mtimecmp[i] <= mtime) begin
                cores[i].pRq.enq(MMIOPRq {
                    target: MTIP,
                    func: St,
                    data: 1
                });
                mtip[i] <= True;
                needTimerInt[i] = True;
            end
        end
        if(needTimerInt != replicate(False)) begin
            state <= WaitResp;
            curReq <= TimerInterrupt;
            waitMTIPCRs <= needTimerInt;
            if(verbose) begin
                $display("[Platform - SelectReq] timer interrupt",
                         ", mtime %x", mtime,
                         ", mtimcmp ", fshow(readVReg(mtimecmp)),
                         ", old mtip ", fshow(readVReg(mtip)),
                         ", new interrupts ", fshow(needTimerInt));
            end
        end
        else begin
            // now check for MMIO req from core
            function Bool hasReq(Integer i) = cores[i].cRq.notEmpty;
            Vector#(CoreNum, Integer) idxVec = genVector;
            if(find(hasReq, idxVec) matches tagged Valid .i) begin
                cores[i].cRq.deq;
                MMIOCRq req = cores[i].cRq.first;
                // record req
                reqCore <= fromInteger(i);
                reqFunc <= req.func;
                reqBE <= req.byteEn;
                reqData <= req.data;
                // find out which MMIO reg/device is being requested
                DataAlignedAddr addr = getDataAlignedAddr(req.addr);
                MMIOPlatformReq newReq = Invalid;
                if(addr >= bootRomBaseAddr && addr < bootRomBoundAddr) begin
                    newReq = BootRom (truncate(addr - bootRomBaseAddr));
                end
                else if(addr >= msipBaseAddr && addr < msipBoundAddr) begin
                    newReq = MSIP (truncate(addr - msipBaseAddr));
                end
                else if(addr >= mtimecmpBaseAddr &&
                        addr < mtimecmpBoundAddr) begin
                    newReq = MTimeCmp (truncate(addr - mtimecmpBaseAddr));
                end
                else if(addr == mtimeBaseAddr) begin
                    // assume mtime is of size Data
                    newReq = MTime;
                end
                else if(addr == toHostAddr) begin
                    // assume tohost is of size Data
                    newReq = ToHost;
                end
                else if(addr == fromHostAddr) begin
                    // assume fromhost is of size Data
                    newReq = FromHost;
                end
                if(newReq != Invalid) begin
                    // process valid req
                    curReq <= newReq;
                    state <= ProcessReq;
                end
                else begin
                    // access fault
                    cores[i].pRs.enq(MMIOPRs {valid: False, data: ?});
                end
                if(verbose) begin
                    $display("[Platform - SelectReq] new req, core %d, req ",
                             i, fshow(req), ", type ", fshow(newReq));
                end
            end
        end
    endrule

    // handle new timer interrupt: wait for writes on MTIP to be done
    rule waitTimerInterruptDone(state == WaitResp && curReq == TimerInterrupt);
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            if(waitMTIPCRs[i]) begin
                cores[i].cRs.deq;
            end
        end
        state <= SelectReq;
        if(verbose) begin
            $display("[Platform - Done] timer interrupt",
                     ", mtip ", fshow(readVReg(mtip)),
                     ", waitCRs ", fshow(waitMTIPCRs));
        end
    endrule

    // handle boot rom access
    rule processBootRom(
        curReq matches tagged BootRom .offset &&& state == ProcessReq
    );
        if(reqFunc == Ld) begin
            bootRom.put(0, offset, ?);
            state <= WaitResp;
        end
        else begin
            // boot rom is read only, access fault
            state <= SelectReq;
            cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
            if(verbose) begin
                $display("[Platform - process boot rom] cannot write");
            end
        end
    endrule

    rule waitBootRom(
        curReq matches tagged BootRom .offset &&& state == WaitResp
    );
        state <= SelectReq;
        cores[reqCore].pRs.enq(MMIOPRs {
            valid: True,
            data: bootRom.read
        });
        if(verbose) begin
            $display("[Platform - boot rom done] data %x", bootRom.read);
        end
    endrule

    // handle MSIP access
    rule processMSIP(
        curReq matches tagged MSIP .offset &&& state == ProcessReq
    );
        // core corresponding to lower bits of requested Data
        CoreId lower_core = truncate({offset, 1'b0});
        Bool lower_en = reqBE[0];
        // core corresponding to upper bits of requested Data. Need to check if
        // this core truly exists
        CoreId upper_core = truncate({offset, 1'b1});
        Bool upper_valid = {offset, 1'b1} < fromInteger(valueof(CoreNum));
        Bool upper_en = reqBE[4];

        if(upper_en && !upper_valid) begin
            // access invalid core's MSIP, fault
            state <= SelectReq;
            cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
            if(verbose) begin
                $display("[Platform - process msip] access invalid core");
            end
        end
        else if(reqFunc matches tagged Amo .amoFunc) begin
            // AMO req: should only access MSIP of one core. Thus, we always
            // treat the accessed core as the lower core to save the shift (AMO
            // resp is different from load that valid data is already shifted
            // to LSBs). Besides, we only use the lower 32 bits of reqData.
            if(lower_en && upper_en) begin
                state <= SelectReq;
                cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
                if(verbose) begin
                    $display("[Platform - process msip] ",
                             "AMO cannot access 2 cores");
                end
            end
            else if(lower_en) begin
                cores[lower_core].pRq.enq(MMIOPRq {
                    target: MSIP,
                    func: reqFunc,
                    data: truncate(reqData)
                });
                waitLowerMSIPCRs <= Valid (lower_core);
                waitUpperMSIPCRs <= Invalid;
                state <= WaitResp;
            end
            else if(upper_en) begin
                cores[upper_core].pRq.enq(MMIOPRq {
                    target: MSIP,
                    func: reqFunc,
                    data: truncate(reqData)
                });
                waitLowerMSIPCRs <= Valid (upper_core);
                waitUpperMSIPCRs <= Invalid;
                state <= WaitResp;
            end
            else begin
                // AMO access nothing: fault
                state <= SelectReq;
                cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
                if(verbose) begin
                    $display("[Platform - process msip] access nothing");
                end
            end
        end
        else begin
            // normal load and store
            if(lower_en) begin
                cores[lower_core].pRq.enq(MMIOPRq {
                    target: MSIP,
                    func: reqFunc,
                    data: zeroExtend(reqData[0])
                });
            end
            if(upper_en) begin
                cores[upper_core].pRq.enq(MMIOPRq {
                    target: MSIP,
                    func: reqFunc,
                    data: zeroExtend(reqData[32])
                });
            end
            state <= WaitResp;
            waitLowerMSIPCRs <= lower_en ? Valid (lower_core) : Invalid;
            waitUpperMSIPCRs <= upper_en ? Valid (upper_core) : Invalid;
        end
    endrule

    rule waitMSIPDone(
        curReq matches tagged MSIP .offset &&& state == WaitResp
    );
        Bit#(32) lower_data = 0;
        Bit#(32) upper_data = 0;
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            if (waitLowerMSIPCRs matches tagged Valid .c &&&
                c == fromInteger(i)) begin
                cores[i].cRs.deq;
                lower_data = zeroExtend(cores[i].cRs.first.data);
            end
            else if(waitUpperMSIPCRs matches tagged Valid .c &&&
                    c == fromInteger(i)) begin
                cores[i].cRs.deq;
                upper_data = zeroExtend(cores[i].cRs.first.data);
            end
        end
        state <= SelectReq;
        cores[reqCore].pRs.enq(MMIOPRs {
            valid: True,
            // for AMO, resp data should be signExtend(lower_data). However,
            // lower_data is just 1 or 0, and upper_data is always 0, so we
            // don't need to do signExtend.
            data: {upper_data, lower_data}
        });
        if(verbose) begin
            $display("[Platform - msip done] lower %x, upper %x",
                     lower_data, upper_data);
        end
    endrule

    function Data getWriteData(Data orig);
        if(reqFunc matches tagged Amo .amoFunc) begin
            // amo
            Bool doubleWord = reqBE[4] && reqBE[0];
            Bool upper32 = reqBE[4] && !reqBE[0];
            let amoInst = AmoInst {
                func: amoFunc,
                doubleWord: doubleWord,
                aq: False,
                rl: False
            };
            return amoExec(amoInst, orig, reqData, upper32);
        end
        else begin
            // normal store
            Vector#(NumBytes, Bit#(8)) data = unpack(orig);
            Vector#(NumBytes, Bit#(8)) wrVec = unpack(reqData);
            for(Integer i = 0; i < valueof(NumBytes); i = i+1) begin
                if(reqBE[i]) begin
                    data[i] = wrVec[i];
                end
            end
            return pack(data);
        end
    endfunction

    function Data getAmoResp(Data orig);
        if(reqBE[4] && reqBE[0]) begin
            // double word
            return orig;
        end
        else if(reqBE[4]) begin
            // upper 32 bit
            return signExtend(orig[63:32]);
        end
        else begin
            // lower 32 bit
            return signExtend(orig[31:0]);
        end
    endfunction

    // handle mtimecmp access
    rule processMTimeCmp(
        curReq matches tagged MTimeCmp .offset &&& state == ProcessReq
    );
        if(offset > fromInteger(valueof(CoreNum) - 1)) begin
            // access invalid core's mtimecmp, fault
            cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
            state <= SelectReq;
            if(verbose) begin
                $display("[Platform - process mtimecmp] access fault");
            end
        end
        else begin
            let oldMTimeCmp = mtimecmp[offset];
            if(reqFunc == Ld) begin
                cores[reqCore].pRs.enq(MMIOPRs {
                    valid: True,
                    data: oldMTimeCmp
                });
                state <= SelectReq;
                if(verbose) begin
                    $display("[Platform - process mtimecmp] read done, data %x",
                             oldMTimeCmp);
                end
            end
            else begin
                // do updates for store or AMO
                let newData = getWriteData(oldMTimeCmp);
                mtimecmp[offset] <= newData;
                // get and record amo resp
                let respData = getAmoResp(oldMTimeCmp);
                amoResp <= respData;
                // check changes to MTIP
                if(newData <= mtime && !mtip[offset]) begin
                    // need to post new timer interrupt
                    mtip[offset] <= True;
                    cores[offset].pRq.enq(MMIOPRq {
                        target: MTIP,
                        func: St,
                        data: 1
                    });
                    state <= WaitResp;
                end
                else if(newData > mtime && mtip[offset]) begin
                    // need to clear timer interrupt
                    mtip[offset] <= False;
                    cores[offset].pRq.enq(MMIOPRq {
                        target: MTIP,
                        func: St,
                        data: 0
                    });
                    state <= WaitResp;
                end
                else begin
                    // nothing happens to mtip, just finish this req
                    cores[reqCore].pRs.enq(MMIOPRs {
                        valid: True,
                        // store doesn't need resp data, just fill in AMO resp
                        data: respData
                    });
                    state <= SelectReq;
                    if(verbose) begin
                        $display("[Platform - process mtimecmp] ",
                                 "no change to mtip ", fshow(readVReg(mtip)),
                                 ", mtime %x", mtime,
                                 ", old mtimecmp ", fshow(readVReg(mtimecmp)),
                                 ", new mtimecmp[%d] %x", offset, newData);
                    end
                end
            end
        end
    endrule

    rule waitMTimeCmpDone(
        curReq matches tagged MTimeCmp .offset &&& state == WaitResp
    );
        cores[offset].cRs.deq;
        cores[reqCore].pRs.enq(MMIOPRs {
            valid: True,
            // store doesn't need resp data, just fill in AMO resp. We cannot
            // recompute AMO resp now, because mtimecmp has changed
            data: amoResp
        });
        state <= SelectReq;
        if(verbose) begin
            $display("[Platform - mtimecmp done]",
                     ", mtime %x", mtime,
                     ", mtimecmp ", fshow(readVReg(mtimecmp)),
                     ", mtip ", fshow(readVReg(mtip)));
        end
    endrule

    // handle mtime access
    rule processMTime(state == ProcessReq && curReq == MTime);
        if(reqFunc == Ld) begin
            cores[reqCore].pRs.enq(MMIOPRs {valid: True, data: mtime});
            state <= SelectReq;
            if(verbose) begin
                $display("[Platform - process mtime] read done, data %x",
                         mtime);
            end
        end
        else begin
            // do update for store or AMO
            let newData = getWriteData(mtime);
            mtime <= newData;
            // get and record AMO resp
            let respData = getAmoResp(mtime);
            amoResp <= respData;
            // check change in MTIP
            Vector#(CoreNum, Bool) changeMTIP = replicate(False);
            for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
                if(mtimecmp[i] <= newData && !mtip[i]) begin
                    cores[i].pRq.enq(MMIOPRq {
                        target: MTIP,
                        func: St,
                        data: 1
                    });
                    changeMTIP[i] = True;
                end
                else if(mtimecmp[i] > newData && mtip[i]) begin
                    cores[i].pRq.enq(MMIOPRq {
                        target: MTIP,
                        func: St,
                        data: 0
                    });
                    changeMTIP[i] = True;
                end
            end
            if(changeMTIP != replicate(False)) begin
                waitMTIPCRs <= changeMTIP;
                state <= WaitResp;
            end
            else begin
                cores[reqCore].pRs.enq(MMIOPRs {
                    valid: True,
                    data: respData // AMO resp
                });
                state <= SelectReq;
                if(verbose) begin
                    $display("[Platform - process mtime] ",
                             "no change to mtip ", fshow(readVReg(mtip)),
                             ", new mtime %x", newData,
                             ", mtimecmp ", fshow(readVReg(mtimecmp)));
                end
            end
        end
    endrule

    rule waitMTimeDone(state == WaitResp && curReq == MTime);
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            if(waitMTIPCRs[i]) begin
                cores[i].cRs.deq;
            end
        end
        cores[reqCore].pRs.enq(MMIOPRs {
            valid: True,
            data: amoResp // recorded amo resp
        });
        state <= SelectReq;
        if(verbose) begin
            $display("[Platform - mtime done]",
                     ", mtime %x", mtime,
                     ", mtimecmp ", fshow(readVReg(mtimecmp)),
                     ", mtip ", fshow(readVReg(mtip)));
        end
    endrule

    // handle tohost access
    rule processToHost(state == ProcessReq && curReq == ToHost);
        let resp = MMIOPRs {valid: False, data: ?};
        if(reqFunc == St) begin
            if(toHostQ.notEmpty) begin
                doAssert(False, "Cannot write tohost when toHostQ not empty");
                // this will raise access fault
            end
            else begin
                let data = getWriteData(0);
                if(data != 0) begin // 0 means nothing for tohost
                    toHostQ.enq(data);
                end
                resp.valid = True;
            end
        end
        else if(reqFunc == Ld) begin
            resp.valid = True;
            if(toHostQ.notEmpty) begin
                resp.data = toHostQ.first;
            end
            else begin
                resp.data = 0;
            end
        end
        else begin
            // amo: access fault
            doAssert(False, "Cannot do AMO on toHost");
        end
        state <= SelectReq;
        cores[reqCore].pRs.enq(resp);
        if(verbose) begin
            $display("[Platform - process tohost] resp ", fshow(resp));
        end
    endrule

    // handle fromhost access
    rule processFromHost(state == ProcessReq && curReq == FromHost);
        let resp = MMIOPRs {valid: False, data: ?};
        if(reqFunc == St) begin
            if(fromHostQ.notEmpty) begin
                if(getWriteData(fromHostQ.first) == 0) begin
                    fromHostQ.deq;
                    resp.valid = True;
                end
                else begin
                    doAssert(False, "Can only write 0 to fromhost");
                end
            end
            else begin
                if(getWriteData(0) == 0) begin
                    resp.valid = True;
                end
                else begin
                    doAssert(False, "Can only write 0 to fromhost");
                end
            end
        end
        else if(reqFunc == Ld) begin
            resp.valid = True;
            if(fromHostQ.notEmpty) begin
                resp.data = fromHostQ.first;
            end
            else begin
                resp.data = 0;
            end
        end
        else begin
            // amo: access fault
            doAssert(False, "Cannot do AMO on fromHost");
        end
        state <= SelectReq;
        cores[reqCore].pRs.enq(resp);
        if(verbose) begin
            $display("[Platform - process fromhost] resp ", fshow(resp));
        end
    endrule

    method Action bootRomInitReq(BootRomIndex idx, Data data) if(state == Init);
        bootRom.put(maxBound, idx, data);
        bootRomInitRespQ.enq(?);
    endmethod

    method ActionValue#(void) bootRomInitResp;
        bootRomInitRespQ.deq;
        return ?;
    endmethod

    method Action start(Addr toHost, Addr fromHost) if(state == Init);
        toHostAddr <= getDataAlignedAddr(toHost);
        fromHostAddr <= getDataAlignedAddr(fromHost);
        state <= SelectReq;
    endmethod

    method ActionValue#(Data) to_host;
        toHostQ.deq;
        return toHostQ.first;
    endmethod
    method Action from_host(Data x);
        fromHostQ.enq(x);
    endmethod
endmodule

import BRAMCore::*;
import Vector::*;
import Fifo::*;
import Types::*;
import ProcTypes::*;
import MMIOAddrs::*;
import MMIOCore::*;
import CacheUtils::*;
import Amo::*;

// MMIO logic at platform (MMIOPlatform)
// XXX Currently all MMIO requests and posts of timer interrupts are handled
// one by one in a blocking manner. This is extremely conservative. Hopefully
// this may help avoid some kernel-level problems.

interface MMIOPlatform;
    method Action bootRomInitReq(BootRomIndex index, Data data);
    method ActionValue#(void) bootRomInitResp;
    method Action start(Addr toHost, Addr fromHost);
    method ActionValue#(Data) to_host;
    method Action from_host(Data x);
endinterface

typedef enum {
    Init,
    SelectReq,
    ProcessReq,
    WaitResp
} MMIOPlatformState deriving(Bits, Eq, FShow);

// MMIO device/reg targed by the core request together with offset within
// reg/device
typedef union tagged {
    void Invalid; // invalid req target
    void TimerInterrupt; // auto-generated timer interrupt
    BootRomIndex BootRom;
    MSIPDataAlignedOffset MSIP;
    MTimCmpDataAlignedOffset MTimeCmp;
    void MTime;
    void ToHost;
    void FromHost;
} MMIOPlatformReq deriving(Bits, Eq, FShow);

module mkMMIOPlatform#(Vector#(CoreNum, MMIOCoreToPlatform) cores)(
    MMIOPlatform
) provisos(
    Bits#(Data, 64) // this module assumes Data is 64-bit wide
);
    Bool verbose = True;

    // boot rom
    BRAM_PORT_BE#(BootRomIndex, Data, NumBytes) bootRom <- mkBRAMCore1BE(
        valueOf(TExp#(LgBootRomSzData)), False
    );
    // mtimecmp
    Vector#(CoreNum, Reg#(Data)) mtimecmp <- replicateM(mkReg(0));
    // mtime
    Reg#(Data) mtime <- mkReg(0);
    // HTIF mem mapped addrs
    Fifo#(1, Data) toHostQ <- mkCFFifo;
    Fifo#(1, Data) fromHostQ <- mkCFFifo;
    Reg#(DataAlignedAddr) toHostAddr <- mkReg(0);
    Reg#(DataAlignedAddr) fromHostAddr <- mkReg(0);

    // state machine
    Reg#(MMIOPlatformState) state <- mkReg(Init);
    // current req (valid when state != Init && state != SelectReq
    Reg#(MMIOPlatformReq) curReq <- mkRegU;
    Reg#(CoreId) reqCore <- mkRegU;
    Reg#(MMIOFunc) reqFunc <- mkRegU;
    Reg#(ByteEn) reqBE <- mkRegU;
    Reg#(Data) reqData <- mkRegU;
    // we need to wait for resp from cores when we need to change MTIP
    Reg#(Vector#(CoreNum, Bool)) waitMTIPCRs <- mkRegU;
    // for MSIP access: lower bits and upper bits of requested memory location
    // correspond to two cores. We need to wait resp from these two cores.
    Reg#(Maybe#(CoreId)) waitLowerMSIPCRs <- mkRegU;
    Reg#(Maybe#(CoreId)) waitUpperMSIPCRs <- mkRegU;
    // in case of AMO on mtime and mtimecmp, resp may be sent after waiting for
    // CRs, we record the AMO resp at processing time
    Reg#(Data) amoResp <- mkRegU;

    // we increment mtime periodically
    Reg#(Bit#(TLog#(CyclesPerTimeInc))) cycle <- mkReg(0);

    // To avoid posting timer interrupt repeatedly, we keep a copy of MTIP
    // here. Since each core cannot write MTIP by CSRXXX inst, the only way to
    // change MTIP is through here.
    Vector#(CoreNum, Reg#(Bool)) mtip <- replicateM(mkReg(False));

    // respQ for boot rom init
    Fifo#(1, void) bootRomInitRespQ <- mkCFFifo;

    // pass mtime to each core
    rule propagateTime(state != Init);
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            cores[i].setTime(mtime);
        end
    endrule

    rule incCycle(
        state != Init &&
        cycle < fromInteger(valueof(CyclesPerTimeInc) - 1)
    );
        cycle <= cycle + 1;
    endrule

    // we don't increment mtime when processing a req
    rule incTime(
        state == SelectReq &&
        cycle >= fromInteger(valueof(CyclesPerTimeInc) - 1)
    );
        cycle <= 0;
        mtime <= mtime + fromInteger(valueof(TicksPerTimeInc));
    endrule

    // since we only process 1 MMIO req or timer interrupt at a time, we can
    // enq/deq all FIFOs in one rule

    (* preempts = "incTime, selectReq" *)
    rule selectReq(state == SelectReq);
        // check for timer interrupt
        Vector#(CoreNum, Bool) needTimerInt = replicate(False);
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            if(!mtip[i] && mtimecmp[i] <= mtime) begin
                cores[i].pRq.enq(MMIOPRq {
                    target: MTIP,
                    write: True,
                    data: 1
                });
                mtip[i] <= True;
                needTimerInt[i] = True;
            end
        end
        if(needTimerInt != replicate(False)) begin
            state <= WaitResp;
            curReq <= TimerInterrupt;
            waitMTIPCRs <= needTimerInt;
            if(verbose) begin
                $display("[Platform - SelectReq] timer interrupt",
                         ", mtime %x", mtime,
                         ", mtimcmp ", fshow(readVReg(mtimecmp)),
                         ", old mtip ", fshow(readVReg(mtip)),
                         ", new interrupts ", fshow(needTimerInt));
            end
        end
        else begin
            // now check for MMIO req from core
            function Bool hasReq(Integer i) = cores[i].cRq.notEmpty;
            Vector#(CoreNum, Integer) idxVec = genVector;
            if(find(hasReq, idxVec) matches tagged Valid .i) begin
                cores[i].cRq.deq;
                MMIOCRq req = cores[i].cRq.first;
                // record req
                reqCore <= fromInteger(i);
                reqFunc <= req.func;
                reqBE <= req.byteEn;
                reqData <= req.data;
                // find out which MMIO reg/device is being requested
                DataAlignedAddr addr = getDataAlignedAddr(req.addr);
                MMIOPlatformReq newReq = Invalid;
                if(addr >= bootRomBaseAddr && addr < bootRomBoundAddr) begin
                    newReq = BootRom (truncate(addr - bootRomBaseAddr));
                end
                else if(addr >= msipBaseAddr && addr < msipBoundAddr) begin
                    newReq = MSIP (truncate(addr - msipBaseAddr));
                end
                else if(addr >= mtimecmpBaseAddr &&
                        addr < mtimecmpBoundAddr) begin
                    newReq = MTimeCmp (truncate(addr - mtimecmpBaseAddr));
                end
                else if(addr == mtimeBaseAddr) begin
                    // assume mtime is of size Data
                    newReq = MTime;
                end
                else if(addr == toHostAddr) begin
                    // assume tohost is of size Data
                    newReq = ToHost;
                end
                else if(addr == fromHostAddr) begin
                    // assume fromhost is of size Data
                    newReq = FromHost;
                end
                if(newReq != Invalid) begin
                    // process valid req
                    curReq <= newReq;
                    state <= ProcessReq;
                end
                else begin
                    // access fault
                    cores[i].pRs.enq(MMIOPRs {valid: False, data: ?});
                end
                if(verbose) begin
                    $display("[Platform - SelectReq] new req, core %d, req ",
                             i, fshow(req), ", type ", fshow(newReq));
                end
            end
        end
    endrule

    // handle new timer interrupt: wait for writes on MTIP to be done
    rule waitTimerInterruptDone(state == WaitResp && curReq == TimerInterrupt);
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            if(waitMTIPCRs[i]) begin
                cores[i].cRs.deq;
            end
        end
        state <= SelectReq;
        if(verbose) begin
            $display("[Platform - Done] timer interrupt",
                     ", mtip ", fshow(readVReg(mtip)),
                     ", waitCRs ", fshow(waitMTIPCRs));
        end
    endrule

    // handle boot rom access
    rule processBootRom(
        curReq matches tagged BootRom .offset &&& state == ProcessReq
    );
        if(reqFunc == Ld) begin
            bootRom.put(0, offset, ?);
            state <= WaitResp;
        end
        else begin
            // boot rom is read only, access fault
            state <= SelectReq;
            cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
            if(verbose) begin
                $display("[Platform - process boot rom] cannot write");
            end
        end
    endrule

    rule waitBootRom(
        curReq matches tagged BootRom .offset &&& state == WaitResp
    );
        state <= SelectReq;
        cores[reqCore].pRs.enq(MMIOPRs {
            valid: True,
            data: bootRom.read
        });
        if(verbose) begin
            $display("[Platform - boot rom done] data %x", bootRom.read);
        end
    endrule

    // handle MSIP access
    rule processMSIP(
        curReq matches tagged MSIP .offset &&& state == ProcessReq
    );
        // core corresponding to lower bits of requested Data
        CoreId lower_core = truncate({offset, 1'b0});
        Bool lower_en = byteEn[0];
        // core corresponding to upper bits of requested Data. Need to check if
        // this core truly exists
        CoreId upper_core = truncate({offset, 1'b1});
        Bool upper_valid = {offset, 1'b1} < fromInteger(valueof(CoreNum));
        Bool upper_en = byteEn[4];

        if(upper_en && !upper_valid) begin
            // access invalid core's MSIP, fault
            state <= SelectReq;
            cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
            if(verbose) begin
                $display("[Platform - process msip] access invalid core");
            end
        end
        else if(reqFunc matches tagged Amo .amoFunc) begin
            // AMO req: should only access MSIP of one core. Thus, we always
            // treat the accessed core as the lower core to save the shift (AMO
            // resp is different from load that valid data is already shifted
            // to LSBs). Besides, we only use the lower 32 bits of reqData.
            if(lower_en && upper_en) begin
                state <= SelectReq;
                cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
                if(verbose) begin
                    $display("[Platform - process msip] ",
                             "AMO cannot access 2 cores");
                end
            end
            else if(lower_en) begin
                cores[lower_core].pRq.enq(MMIOPRq {
                    target: MSIP,
                    func: reqFunc,
                    data: truncate(reqData)
                });
                waitLowerMSIPCRs <= Valid (lower_core);
                waitUpperMSIPCRs <= Invalid;
                state <= WaitResp;
            end
            else if(upper_en) begin
                cores[upper_core].pRq.enq(MMIOPRq {
                    target: MSIP,
                    func: reqFunc,
                    data: truncate(reqData)
                });
                waitLowerMSIPCRs <= Valid (upper_core);
                waitUpperMSIPCRs <= Invalid;
                state <= WaitResp;
            end
            else begin
                // AMO access nothing: fault
                state <= SelectCRq;
                cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
                if(verbose) begin
                    $display("[Platform - process msip] access nothing");
                end
            end
        end
        else begin
            // normal load and store
            if(lower_en) begin
                cores[lower_core].pRq.enq(MMIOPRq {
                    target: MSIP,
                    func: reqFunc,
                    data: zeroExtend(reqData[0])
                });
            end
            if(upper_en) begin
                cores[upper_core].pRq.enq(MMIOPRq {
                    target: MSIP,
                    func: reqFunc,
                    data: zeroExtend(reqData[32])
                });
            end
            state <= WaitResp;
            waitLowerMSIPCRs <= lower_en ? Valid (lower_core) : Invalid;
            waitUpperMSIPCRs <= upper_en ? Valid (upper_core) : Invalid;
        end
    endrule

    rule waitMSIPDone(
        curReq matches tagged MSIP .offset &&& state == WaitResp
    );
        Bit#(32) lower_data = 0;
        Bit#(32) upper_data = 0;
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            if (waitLowerMSIPCRs matches tagged Valid .c &&&
                c == fromInteger(i)) begin
                cores[i].cRs.deq;
                lower_data = zeroExtend(cores[i].cRs.first.data);
            end
            else if(waitUpperMSIPCRs matches tagged Valid .c &&&
                    c == fromInteger(i)) begin
                cores[i].cRs.deq;
                upper_data = zeroExtend(cores[i].cRs.first.data);
            end
        end
        state <= SelectReq;
        cores[reqCore].pRs.enq(MMIOPRs {
            valid: True,
            // for AMO, resp data should be signExtend(lower_data). However,
            // lower_data is just 1 or 0, and upper_data is always 0, so we
            // don't need to do signExtend.
            data: {upper_data, lower_data}
        });
        if(verbose) begin
            $display("[Platform - msip done] lower %x, upper %x",
                     lower_data, upper_data);
        end
    endrule

    function Data getWriteData(Data orig);
        if(reqFunc matches tagged Amo .amoFunc) begin
            // amo
            Bool doubleWord = reqBE[4] && reqBE[0];
            Bool upper32 = reqBE[4] && !reqBE[0];
            let amoInst = AmoInst {
                func: amoFunc,
                doubleWord: doubleWord,
                aq: False,
                rl: False
            };
            return amoExec(amoInst, orig, reqData, upper32);
        end
        else begin
            // normal store
            Vector#(NumBytes, Bit#(8)) data = unpack(orig);
            Vector#(NumBytes, Bit#(8)) wrVec = unpack(reqData);
            for(Integer i = 0; i < valueof(NumBytes); i = i+1) begin
                if(reqBE[i]) begin
                    data[i] = wrVec[i];
                end
            end
            return pack(data);
        end
    endfunction

    function Data getAmoResp(Data orig);
        if(reqBE[4] && reqBE[0]) begin
            // double word
            return orig;
        end
        else if(reqBE[4]) begin
            // upper 32 bit
            return signExtend(orig[63:32]);
        end
        else begin
            // lower 32 bit
            return signExtend(orig[31:0]);
        end
    endfunction

    // handle mtimecmp access
    rule processMTimeCmp(
        curReq matches tagged MTimeCmp .offset &&& state == ProcessReq
    );
        if(offset > fromInteger(valueof(CoreNum) - 1)) begin
            // access invalid core's mtimecmp, fault
            cores[reqCore].pRs.enq(MMIOPRs {valid: False, data: ?});
            state <= SelectReq;
            if(verbose) begin
                $display("[Platform - process mtimecmp] access fault");
            end
        end
        else begin
            let oldMTimeCmp = mtimecmp[offset];
            if(reqFunc == Ld) begin
                cores[reqCore].pRs.enq(MMIOPRs {
                    valid: True,
                    data: oldMTimeCmp
                });
                state <= SelectReq;
                if(verbose) begin
                    $display("[Platform - process mtimecmp] read done, data %x",
                             oldMTimeCmp);
                end
            end
            else begin
                // do updates for store or AMO
                let newData = getWriteData(oldMTimeCmp);
                mtimecmp[offset] <= newData;
                // get and record amo resp
                let respData = getAmoResp(oldMTimeCmp);
                amoResp <= respData;
                // check changes to MTIP
                if(newData <= mtime && !mtip[offset]) begin
                    // need to post new timer interrupt
                    mtip[offset] <= True;
                    cores[offset].pRq.enq(MMIOPRq {
                        target: MTIP,
                        write: True,
                        data: 1
                    });
                    state <= WaitResp;
                end
                else if(newData > mtime && mtip[offset]) begin
                    // need to clear timer interrupt
                    mtip[offset] <= False;
                    cores[offset].pRq.enq(MMIOPRq {
                        target: MTIP,
                        write: True,
                        data: 0
                    });
                    state <= WaitResp;
                end
                else begin
                    // nothing happens to mtip, just finish this req
                    cores[reqCore].pRs.enq(MMIOPRs {
                        valid: True,
                        // store doesn't need resp data, just fill in AMO resp
                        data: respData
                    });
                    state <= SelectReq;
                    if(verbose) begin
                        $display("[Platform - process mtimecmp] ",
                                 "no change to mtip ", fshow(readVReg(mtip)),
                                 ", mtime %x", mtime,
                                 ", old mtimecmp ", fshow(readVReg(mtimecmp)),
                                 ", new mtimecmp[%d] %x", offset, newData);
                    end
                end
            end
        end
    endrule

    rule waitMTimeCmpDone(
        curReq matches tagged MTimeCmp .offset &&& state == WaitResp
    );
        cores[offset].cRs.deq;
        cores[reqCore].pRs.enq(MMIOPRs {
            valid: True,
            // store doesn't need resp data, just fill in AMO resp. We cannot
            // recompute AMO resp now, because mtimecmp has changed
            data: amoResp
        });
        state <= SelectReq;
        if(verbose) begin
            $display("[Platform - mtimecmp done]",
                     ", mtime %x", mtime,
                     ", mtimecmp ", fshow(readVReg(mtimecmp)),
                     ", mtip ", fshow(readVReg(mtip)));
        end
    endrule

    // handle mtime access
    rule processMTime(state == ProcessReq && curReq == MTime);
        if(reqFunc == Ld) begin
            cores[reqCore].pRs.enq(MMIOPRs {valid: True, data: mtime});
            state <= SelectReq;
            if(verbose) begin
                $display("[Platform - process mtime] read done, data %x",
                         mtime);
            end
        end
        else begin
            // do update for store or AMO
            let newData = getWriteData(mtime);
            mtime <= newData;
            // get and record AMO resp
            let respData = getAmoResp(mtime);
            amoResp <= respData;
            // check change in MTIP
            Vector#(CoreNum, Bool) changeMTIP = replicate(False);
            for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
                if(mtimecmp[i] <= newData && !mtip[i]) begin
                    cores[i].pRq.enq(MMIOPRq {
                        target: MTIP,
                        write: True,
                        data: 1
                    });
                    changeMTIP[i] = True;
                end
                else if(mtimecmp[i] > newData && mtip[i]) begin
                    cores[i].pRq.enq(MMIOPRq {
                        target: MTIP,
                        write: True,
                        data: 0
                    });
                    changeMTIP[i] = True;
                end
            end
            if(changeMTIP != replicate(False)) begin
                waitMTIPCRs <= changeMTIP;
                state <= WaitResp;
            end
            else begin
                cores[reqCore].pRs.enq(MMIOPRs {
                    valid: True,
                    data: respData // AMO resp
                });
                state <= SelectReq;
                if(verbose) begin
                    $display("[Platform - process mtime] ",
                             "no change to mtip ", fshow(readVReg(mtip)),
                             ", new mtime %x", newData,
                             ", mtimecmp ", fshow(readVReg(mtimecmp)));
                end
            end
        end
    endrule

    rule waitMTimeDone(state == WaitResp && curReq == MTime);
        for(Integer i = 0; i < valueof(CoreNum); i = i+1) begin
            if(waitMTIPCRs[i]) begin
                cores[i].cRs.deq;
            end
        end
        cores[reqCore].pRs.enq(MMIOPRs {
            valid: True,
            data: amoResp // recorded amo resp
        });
        state <= SelectReq;
        if(verbose) begin
            $display("[Platform - mtime done]",
                     ", mtime %x", mtime,
                     ", mtimecmp ", fshow(readVReg(mtimecmp)),
                     ", mtip ", fshow(readVReg(mtip)));
        end
    endrule

    // handle tohost access
    rule processToHost(state == ProcessReq && curReq == ToHost);
        let resp = MMIOPRs {valid: False, data: ?};
        if(reqFunc == St) begin
            if(toHostQ.notEmpty) begin
                doAssert(False, "Cannot write tohost when toHostQ not empty");
                // this will raise access fault
            end
            else begin
                let data = getWriteData(0);
                if(data != 0) begin // 0 means nothing for tohost
                    toHostQ.enq(data);
                end
                resp.valid = True;
            end
        end
        else if(reqFunc == Ld) begin
            resp.valid = True;
            if(toHostQ.notEmpty) begin
                resp.data = toHostQ.first;
            end
            else begin
                resp.data = 0;
            end
        end
        else begin
            // amo: access fault
            doAssert(False, "Cannot do AMO on toHost");
        end
        state <= SelectReq;
        cores[reqCore].pRs.enq(resp);
        if(verbose) begin
            $display("[Platform - process tohost] resp ", fshow(resp));
        end
    endrule

    rule writeToHostStuck(state == ProcessReq && curReq == ToHost &&
                       isWrite && toHostQ.notEmpty);
        $display("[Platform - process tohost] WARNING: ",
                 "write when toHostQ not empty");
    endrule

    // handle fromhost access
    rule processFromHost(state == ProcessReq && curReq == FromHost);
        let resp = MMIOPRs {valid: False, data: ?};
        if(reqFunc == St) begin
            if(fromHostQ.notEmpty) begin
                if(getWriteData(fromHostQ.first) == 0) begin
                    fromHostQ.deq;
                    resp.valid = True;
                end
                else begin
                    doAssert(False, "Can only write 0 to fromhost");
                end
            end
            else begin
                if(getWriteData(0) == 0) begin
                    resp.valid = True;
                end
                else begin
                    doAssert(False, "Can only write 0 to fromhost");
                end
            end
        end
        else if(reqFunc == Ld) begin
            resp.valid = True;
            if(fromHostQ.notEmpty) begin
                resp.data = fromHostQ.first;
            end
            else begin
                resp.data = 0;
            end
        end
        else begin
            // amo: access fault
            doAssert(False, "Cannot do AMO on fromHost");
        end
        state <= SelectReq;
        cores[reqCore].pRs.enq(resp);
        if(verbose) begin
            $display("[Platform - process fromhost] resp ", fshow(resp));
        end
    endrule

    method Action bootRomInitReq(BootRomIndex idx, Data data) if(state == Init);
        bootRom.put(maxBound, idx, data);
        bootRomInitRespQ.enq(?);
    endmethod

    method ActionValue#(void) bootRomInitResp;
        bootRomInitRespQ.deq;
        return ?;
    endmethod

    method Action start(Addr toHost, Addr fromHost) if(state == Init);
        toHostAddr <= getDataAlignedAddr(toHost);
        fromHostAddr <= getDataAlignedAddr(fromHost);
        state <= SelectReq;
    endmethod

    method ActionValue#(Data) to_host;
        toHostQ.deq;
        return toHostQ.first;
    endmethod
    method Action from_host(Data x);
        fromHostQ.enq(x);
    endmethod
endmodule


removed command funnel

removed command funnel

// Copyright (c) 2013 Quanta Research Cambridge, Inc.

// 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.

import Vector::*;
import FIFOF::*;
import FIFO::*;
import GetPut::*;
import Assert::*;
import ClientServer::*;
import BRAM::*;
import BRAMFIFO::*;
import ConfigCounter::*;
import Connectable::*;

import ConnectalMemory::*;
import MemTypes::*;
import Pipe::*;
import MemUtils::*;

module mkMemreadEngine(MemreadEngine#(dataWidth, cmdQDepth, numServers))
   provisos( Mul#(TDiv#(dataWidth, 8), 8, dataWidth)
	    ,Add#(1, a__, numServers)
	    ,Add#(b__, TLog#(numServers), TAdd#(1, TLog#(TMul#(cmdQDepth,numServers))))
	    ,Add#(c__, TLog#(numServers), TLog#(TMul#(cmdQDepth, numServers)))
	    ,Add#(d__, TLog#(numServers), 6)
	    );
   let rv <- mkMemreadEngineBuff(valueOf(TExp#(BurstLenSize)));
   return rv;
endmodule

module mkMemreadEngineBuff#(Integer bufferSizeBytes) (MemreadEngine#(dataWidth, cmdQDepth, numServers))
   provisos (Div#(dataWidth,8,dataWidthBytes),
	     Mul#(dataWidthBytes,8,dataWidth),
	     Log#(dataWidthBytes,beatShift),
	     Log#(cmdQDepth,logCmdQDepth),
	     Mul#(cmdQDepth,numServers,cmdBuffSz),
	     Log#(cmdBuffSz, cmdBuffAddrSz),
	     Log#(numServers, serverIdxSz),
	     Add#(1,logCmdQDepth, outCntSz),
	     Add#(1, c__, numServers),
	     Add#(b__, TLog#(numServers), cmdBuffAddrSz),
	     Add#(e__, TLog#(numServers), TAdd#(1, cmdBuffAddrSz)),
	     Add#(a__, serverIdxSz, cmdBuffAddrSz),
	     Min#(2,TLog#(numServers),bpc),
	     Add#(d__, TLog#(numServers), TAdd#(1, serverIdxSz)),
	     Add#(f__, serverIdxSz, 6));

   let verbose = False;

   Integer bufferSizeBeats = bufferSizeBytes/valueOf(dataWidthBytes);
   Vector#(numServers, Reg#(Bool))               outs1 <- replicateM(mkReg(False));
   Vector#(numServers, Reg#(Bit#(outCntSz)))     outs0 <- replicateM(mkReg(0));
   Vector#(numServers, ConfigCounter#(16))     buffCap <- replicateM(mkConfigCounter(fromInteger(bufferSizeBeats)));
   UGBramFifos#(numServers,cmdQDepth,MemengineCmd) cmdBuf <- mkUGBramFifos;

   Reg#(Bool) load_in_progress <- mkReg(False);
   FIFO#(Tuple3#(MemengineCmd,Bool,Bool))         loadf_b <- mkSizedFIFO(1);
   FIFO#(Tuple2#(Bit#(serverIdxSz),MemengineCmd)) loadf_c <- mkSizedFIFO(1);
   FIFO#(Tuple3#(Bit#(8),Bit#(serverIdxSz),Bool))   workf <- mkSizedBRAMFIFO(32); // isthis the right size?

   Vector#(numServers, FIFO#(void))              outfs <- replicateM(mkSizedFIFO(1));
   Vector#(numServers, FIFOF#(MemengineCmd))   cmds_in <- replicateM(mkSizedFIFOF(1));

   FIFOF#(MemData#(dataWidth))                             read_data <- mkFIFOF;
   Vector#(numServers, FIFOF#(MemData#(dataWidth)))  read_data_buffs <- replicateM(mkSizedBRAMFIFOF(bufferSizeBeats));
   function PipeOut#(Bit#(dataWidth)) check_out(PipeOut#(MemData#(dataWidth)) inpipe, Integer i) =
      (interface PipeOut;
	  method Bit#(dataWidth) first;
	     return inpipe.first.data;
	  endmethod
	  method Action deq;
	     inpipe.deq;
	     buffCap[i].increment(1);
	     if (inpipe.first.last)
		outfs[i].enq(?);
	  endmethod
	  method Bool notEmpty = inpipe.notEmpty;
       endinterface);
   Vector#(numServers, PipeOut#(Bit#(dataWidth))) read_data_pipes = zipWith(check_out, map(toPipeOut,read_data_buffs), genVector);

   Reg#(Bit#(8))                    respCnt <- mkReg(0);
   Reg#(Bit#(TAdd#(1,serverIdxSz))) loadIdx <- mkReg(0);
   let beat_shift = fromInteger(valueOf(beatShift));
   let cmd_q_depth = fromInteger(valueOf(cmdQDepth));

   function Action incr_loadIdx =
      (action
       if(loadIdx+1 >= fromInteger(valueOf(numServers)))
	  loadIdx <= 0;
       else
	  loadIdx <= loadIdx+1;
       endaction);

   for (Integer idx = 0; idx < valueOf(numServers); idx = idx + 1)
      rule store_cmd;
	 let cmd <- toGet(cmds_in[idx]).get();
	 outs1[idx] <= True;
	 cmdBuf.enq(fromInteger(idx),cmd);
	 if (verbose) $display("mkMemreadEngineBuff::store_cmd %d %d", idx, buffCap[idx].read);
      endrule

   rule load_ctxt_a (!load_in_progress);
      if (outs1[loadIdx]) begin
	 load_in_progress <= True;
	 cmdBuf.first_req(truncate(loadIdx));
	 if (verbose) $display("mkMemreadEngineBuff::load_ctxt_a %d", loadIdx);
      end
      else begin
	 incr_loadIdx;
      end
   endrule

   rule load_ctxt_b;
      let cmd <- cmdBuf.first_resp;
      let cond0 <- buffCap[loadIdx].maybeDecrement(unpack(extend(cmd.burstLen>>beat_shift)));
      let cond1 = cmd.len <= extend(cmd.burstLen);
      loadf_b.enq(tuple3(cmd,cond0,cond1));
      if (verbose) $display("mkMemreadEngineBuff::load_ctxt_b %d %d", buffCap[loadIdx].read(), cmd.burstLen>>beat_shift);
   endrule

   rule load_ctxt_c;
      load_in_progress <= False;
      incr_loadIdx;
      match {.cmd,.cond0,.cond1} <- toGet(loadf_b).get;
      if  (cond0) begin
	 if (verbose) $display("mkMemreadEngineBuff::load_ctxt_c %d %d", cmd.len, loadIdx);
	 let x = cmd.burstLen;
	 if (cmd.len < extend(cmd.burstLen))
	    x = truncate(cmd.len);
	 loadf_c.enq(tuple2(truncate(loadIdx),cmd));
	 if (cond1) begin
	    //$display("load_ctxt_b cond1");
	    outs1[loadIdx] <= False;
	    cmdBuf.deq(truncate(loadIdx));
	 end
	 else begin
	    let new_cmd = MemengineCmd{sglId:cmd.sglId, base:cmd.base+extend(cmd.burstLen),
				       burstLen:cmd.burstLen, len:cmd.len-extend(cmd.burstLen), tag:cmd.tag};
	    cmdBuf.upd_head(truncate(loadIdx),new_cmd);
	 end
      end
   endrule

   rule read_data_rule;
      let d <- toGet(read_data).get();
      match {.rc, .idx, .last} = workf.first;
      let new_respCnt = respCnt+1;
      let l = False;
      //$display("%h %d", d.data, idx);
      if (new_respCnt == rc) begin
	 respCnt <= 0;
	 workf.deq;
	 //$display("eob %d", idx);
	 l = last;
      end
      else begin
	 respCnt <= new_respCnt;
      end
      read_data_buffs[idx].enq(d);
   endrule

   function MemreadServer#(dataWidth) toMemreadServer(Server#(MemengineCmd,Bool) cs, PipeOut#(Bit#(dataWidth)) p) =
      (interface MemreadServer;
	  interface cmdServer = cs;
	  interface dataPipe  = p;
       endinterface);

   Vector#(numServers, Server#(MemengineCmd,Bool)) rs;
   for(Integer i = 0; i < valueOf(numServers); i=i+1)
      rs[i] = (interface Server#(MemengineCmd,Bool);
		  interface Put request;
		     method Action put(MemengineCmd cmd) if (outs0[i] < cmd_q_depth);
			Bit#(32) bsb = fromInteger(bufferSizeBytes);
`ifdef BSIM
			Bit#(32) dw = fromInteger(valueOf(dataWidthBytes));
			let mdw = ((cmd.len)/dw)*dw != cmd.len;
			let bbl = extend(cmd.burstLen) > bsb;
			if(bbl || mdw) begin
			   if (bbl)
			      $display("XXXXXXXXXX mkMemreadEngineBuff::unsupported burstLen %d %d", bsb, cmd.burstLen);
			   if (mdw)
			      $display("XXXXXXXXXX mkMemreadEngineBuff::unsupported len %d", cmd.len);
			end
			else begin
`endif
	 		   outs0[i] <= outs0[i]+1;
			   cmds_in[i].enq(cmd);
`ifdef BSIM
			end
`endif
 		     endmethod
		  endinterface
		  interface Get response;
		     method ActionValue#(Bool) get;
			outfs[i].deq;
	 		outs0[i] <= outs0[i]-1;
			return True;
		     endmethod
		  endinterface
	       endinterface);
   interface readServers = rs;
   interface MemReadClient dmaClient;
      interface Get readReq;
	 method ActionValue#(MemRequest) get();
	    match {.idx, .cmd} <- toGet(loadf_c).get;
	    Bit#(BurstLenSize) bl = cmd.burstLen;
	    let last = False;
	    if (cmd.len <= extend(bl)) begin
	       last = True;
	       bl = truncate(cmd.len);
	    end
	    workf.enq(tuple3(truncate(bl>>beat_shift), idx, last));
	    //$display("readReq %d, %h %h %h", idx, cmd.base, bl, last);
	    return MemRequest { sglId: cmd.sglId, offset: cmd.base, burstLen:bl, tag: (cmd.tag << valueOf(serverIdxSz)) | extend(idx)};
	 endmethod
      endinterface
      interface Put readData = toPut(read_data);
   endinterface
   interface dataPipes = read_data_pipes;
   interface read_servers = zipWith(toMemreadServer, rs, read_data_pipes);
endmodule

// Copyright (c) 2013 Quanta Research Cambridge, Inc.

// 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.

import Vector::*;
import FIFOF::*;
import FIFO::*;
import GetPut::*;
import Assert::*;
import ClientServer::*;
import BRAM::*;
import BRAMFIFO::*;
import ConfigCounter::*;
import Connectable::*;

import ConnectalMemory::*;
import MemTypes::*;
import Pipe::*;
import MemUtils::*;

module mkMemreadEngine(MemreadEngine#(dataWidth, cmdQDepth, numServers))
   provisos( Mul#(TDiv#(dataWidth, 8), 8, dataWidth)
	    ,Add#(1, a__, numServers)
	    ,Add#(b__, TLog#(numServers), TAdd#(1, TLog#(TMul#(cmdQDepth,numServers))))
	    ,Pipe::FunnelPipesPipelined#(1, numServers,Tuple2#(Bit#(TLog#(numServers)), MemTypes::MemengineCmd), TMin#(2,TLog#(numServers)))
	    ,Add#(c__, TLog#(numServers), TLog#(TMul#(cmdQDepth, numServers)))
	    ,Add#(d__, TLog#(numServers), 6)
	    );
   let rv <- mkMemreadEngineBuff(valueOf(TExp#(BurstLenSize)));
   return rv;
endmodule

module mkMemreadEngineBuff#(Integer bufferSizeBytes) (MemreadEngine#(dataWidth, cmdQDepth, numServers))
   provisos (Div#(dataWidth,8,dataWidthBytes),
	     Mul#(dataWidthBytes,8,dataWidth),
	     Log#(dataWidthBytes,beatShift),
	     Log#(cmdQDepth,logCmdQDepth),
	     Mul#(cmdQDepth,numServers,cmdBuffSz),
	     Log#(cmdBuffSz, cmdBuffAddrSz),
	     Log#(numServers, serverIdxSz),
	     Add#(1,logCmdQDepth, outCntSz),
	     Add#(1, c__, numServers),
	     Add#(b__, TLog#(numServers), cmdBuffAddrSz),
	     Add#(e__, TLog#(numServers), TAdd#(1, cmdBuffAddrSz)),
	     Add#(a__, serverIdxSz, cmdBuffAddrSz),
	     Min#(2,TLog#(numServers),bpc),
	     FunnelPipesPipelined#(1,numServers,Tuple2#(Bit#(serverIdxSz),MemengineCmd),bpc),
	     Add#(d__, TLog#(numServers), TAdd#(1, serverIdxSz)),
	     Add#(f__, serverIdxSz, 6));

   let verbose = False;

   Integer bufferSizeBeats = bufferSizeBytes/valueOf(dataWidthBytes);
   Vector#(numServers, Reg#(Bool))               outs1 <- replicateM(mkReg(False));
   Vector#(numServers, Reg#(Bit#(outCntSz)))     outs0 <- replicateM(mkReg(0));
   Vector#(numServers, ConfigCounter#(16))     buffCap <- replicateM(mkConfigCounter(fromInteger(bufferSizeBeats)));
   UGBramFifos#(numServers,cmdQDepth,MemengineCmd) cmdBuf <- mkUGBramFifos;

   Reg#(Bool) load_in_progress <- mkReg(False);
   FIFO#(Tuple3#(MemengineCmd,Bool,Bool))         loadf_b <- mkSizedFIFO(1);
   FIFO#(Tuple2#(Bit#(serverIdxSz),MemengineCmd)) loadf_c <- mkSizedFIFO(1);
   FIFO#(Tuple3#(Bit#(8),Bit#(serverIdxSz),Bool))   workf <- mkSizedBRAMFIFO(32); // isthis the right size?

   Vector#(numServers, FIFO#(void))              outfs <- replicateM(mkSizedFIFO(1));
   Vector#(numServers, FIFOF#(Tuple2#(Bit#(serverIdxSz), MemengineCmd))) cmds_in <- replicateM(mkSizedFIFOF(1));
   FunnelPipe#(1, numServers, Tuple2#(Bit#(serverIdxSz), MemengineCmd),bpc) cmds_in_funnel <- mkFunnelPipesPipelined(map(toPipeOut,cmds_in));

   FIFOF#(MemData#(dataWidth))                             read_data <- mkFIFOF;
   Vector#(numServers, FIFOF#(MemData#(dataWidth)))  read_data_buffs <- replicateM(mkSizedBRAMFIFOF(bufferSizeBeats));
   function PipeOut#(Bit#(dataWidth)) check_out(PipeOut#(MemData#(dataWidth)) inpipe, Integer i) =
      (interface PipeOut;
	  method Bit#(dataWidth) first;
	     return inpipe.first.data;
	  endmethod
	  method Action deq;
	     inpipe.deq;
	     buffCap[i].increment(1);
	     if (inpipe.first.last)
		outfs[i].enq(?);
	  endmethod
	  method Bool notEmpty = inpipe.notEmpty;
       endinterface);
   Vector#(numServers, PipeOut#(Bit#(dataWidth))) read_data_pipes = zipWith(check_out, map(toPipeOut,read_data_buffs), genVector);

   Reg#(Bit#(8))                    respCnt <- mkReg(0);
   Reg#(Bit#(TAdd#(1,serverIdxSz))) loadIdx <- mkReg(0);
   let beat_shift = fromInteger(valueOf(beatShift));
   let cmd_q_depth = fromInteger(valueOf(cmdQDepth));

   function Action incr_loadIdx =
      (action
       if(loadIdx+1 >= fromInteger(valueOf(numServers)))
	  loadIdx <= 0;
       else
	  loadIdx <= loadIdx+1;
       endaction);

   rule store_cmd;
      match {.idx, .cmd} <- toGet(cmds_in_funnel[0]).get;
      outs1[idx] <= True;
      cmdBuf.enq(idx,cmd);
      if (verbose) $display("mkMemreadEngineBuff::store_cmd %d %d", idx, buffCap[idx].read);
   endrule

   rule load_ctxt_a (!load_in_progress);
      if (outs1[loadIdx]) begin
	 load_in_progress <= True;
	 cmdBuf.first_req(truncate(loadIdx));
	 if (verbose) $display("mkMemreadEngineBuff::load_ctxt_a %d", loadIdx);
      end
      else begin
	 incr_loadIdx;
      end
   endrule

   rule load_ctxt_b;
      let cmd <- cmdBuf.first_resp;
      let cond0 <- buffCap[loadIdx].maybeDecrement(unpack(extend(cmd.burstLen>>beat_shift)));
      let cond1 = cmd.len <= extend(cmd.burstLen);
      loadf_b.enq(tuple3(cmd,cond0,cond1));
      if (verbose) $display("mkMemreadEngineBuff::load_ctxt_b %d %d", buffCap[loadIdx].read(), cmd.burstLen>>beat_shift);
   endrule

   rule load_ctxt_c;
      load_in_progress <= False;
      incr_loadIdx;
      match {.cmd,.cond0,.cond1} <- toGet(loadf_b).get;
      if  (cond0) begin
	 if (verbose) $display("mkMemreadEngineBuff::load_ctxt_c %d %d", cmd.len, loadIdx);
	 let x = cmd.burstLen;
	 if (cmd.len < extend(cmd.burstLen))
	    x = truncate(cmd.len);
	 loadf_c.enq(tuple2(truncate(loadIdx),cmd));
	 if (cond1) begin
	    //$display("load_ctxt_b cond1");
	    outs1[loadIdx] <= False;
	    cmdBuf.deq(truncate(loadIdx));
	 end
	 else begin
	    let new_cmd = MemengineCmd{sglId:cmd.sglId, base:cmd.base+extend(cmd.burstLen),
				       burstLen:cmd.burstLen, len:cmd.len-extend(cmd.burstLen), tag:cmd.tag};
	    cmdBuf.upd_head(truncate(loadIdx),new_cmd);
	 end
      end
   endrule

   rule read_data_rule;
      let d <- toGet(read_data).get();
      match {.rc, .idx, .last} = workf.first;
      let new_respCnt = respCnt+1;
      let l = False;
      //$display("%h %d", d.data, idx);
      if (new_respCnt == rc) begin
	 respCnt <= 0;
	 workf.deq;
	 //$display("eob %d", idx);
	 l = last;
      end
      else begin
	 respCnt <= new_respCnt;
      end
      read_data_buffs[idx].enq(d);
   endrule

   function MemreadServer#(dataWidth) toMemreadServer(Server#(MemengineCmd,Bool) cs, PipeOut#(Bit#(dataWidth)) p) =
      (interface MemreadServer;
	  interface cmdServer = cs;
	  interface dataPipe  = p;
       endinterface);

   Vector#(numServers, Server#(MemengineCmd,Bool)) rs;
   for(Integer i = 0; i < valueOf(numServers); i=i+1)
      rs[i] = (interface Server#(MemengineCmd,Bool);
		  interface Put request;
		     method Action put(MemengineCmd c) if (outs0[i] < cmd_q_depth);
			Bit#(32) bsb = fromInteger(bufferSizeBytes);
`ifdef BSIM
			Bit#(32) dw = fromInteger(valueOf(dataWidthBytes));
			let mdw = ((c.len)/dw)*dw != c.len;
			let bbl = extend(c.burstLen) > bsb;
			if(bbl || mdw) begin
			   if (bbl)
			      $display("XXXXXXXXXX mkMemreadEngineBuff::unsupported burstLen %d %d", bsb, c.burstLen);
			   if (mdw)
			      $display("XXXXXXXXXX mkMemreadEngineBuff::unsupported len %d", c.len);
			end
			else begin
`endif
	 		   outs0[i] <= outs0[i]+1;
			   cmds_in[i].enq(tuple2(fromInteger(i),c));
`ifdef BSIM
			end
`endif
 		     endmethod
		  endinterface
		  interface Get response;
		     method ActionValue#(Bool) get;
			outfs[i].deq;
	 		outs0[i] <= outs0[i]-1;
			return True;
		     endmethod
		  endinterface
	       endinterface);
   interface readServers = rs;
   interface MemReadClient dmaClient;
      interface Get readReq;
	 method ActionValue#(MemRequest) get();
	    match {.idx, .cmd} <- toGet(loadf_c).get;
	    Bit#(BurstLenSize) bl = cmd.burstLen;
	    let last = False;
	    if (cmd.len <= extend(bl)) begin
	       last = True;
	       bl = truncate(cmd.len);
	    end
	    workf.enq(tuple3(truncate(bl>>beat_shift), idx, last));
	    //$display("readReq %d, %h %h %h", idx, cmd.base, bl, last);
	    return MemRequest { sglId: cmd.sglId, offset: cmd.base, burstLen:bl, tag: (cmd.tag << valueOf(serverIdxSz)) | extend(idx)};
	 endmethod
      endinterface
      interface Put readData = toPut(read_data);
   endinterface
   interface dataPipes = read_data_pipes;
   interface read_servers = zipWith(toMemreadServer, rs, read_data_pipes);
endmodule


export Ddr3Pins

export Ddr3Pins

// Copyright (c) 2015 Connectal Project

// 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.

import Clocks          ::*;
import GetPut          ::*;
import ClientServer    ::*;
import AxiDdr3Wrapper  ::*;
import AxiBits         ::*;
import AxiGather       ::*;
import Axi4MasterSlave ::*;
import ConnectalClocks ::*;
`include "ConnectalProjectConfig.bsv"

typedef 30 Ddr3AddrWidth;
typedef 512 Ddr3DataWidth;

interface Ddr3Pins;
   interface AxiDdr3Ddr3 ddr3;
   interface Clock sysclk_deleteme_unused_clock;
   interface Reset sysrst_deleteme_unused_reset;
endinterface

interface Ddr3;
   interface Axi4Slave#(Ddr3AddrWidth,Ddr3DataWidth,6) slave;
   interface Ddr3Pins ddr3; // pins
   interface Clock uiClock;
   interface Reset uiReset;
endinterface

function Axi4SlaveBits#(Ddr3AddrWidth,Ddr3DataWidth,6,Empty) toAxiSlaveBits(AxiDdr3S_axi s_axi);
   return (interface Axi4SlaveBits;
      method araddr = s_axi.araddr;
      method arburst = s_axi.arburst;
      method arcache = s_axi.arcache;
      method Bit#(1) aresetn(); return 1; endmethod
      method arid = s_axi.arid;
      method arlen = s_axi.arlen;
      method Action arlock(Bit#(2) l); s_axi.arlock(truncate(l)); endmethod //unused
      method arprot = s_axi.arprot;
      method arqos = s_axi.arqos;
      method arready = s_axi.arready;
      method arsize = s_axi.arsize;
      method arvalid = s_axi.arvalid;
      method awaddr = s_axi.awaddr;
      method awburst = s_axi.awburst;
      method awcache = s_axi.awcache;
      method awid = s_axi.awid;
      method awlen = s_axi.awlen;
      method Action awlock(Bit#(2) l); s_axi.awlock(truncate(l)); endmethod //unused
      method awprot = s_axi.awprot;
      method awqos = s_axi.awqos;
      method awready = s_axi.awready;
      method awsize = s_axi.awsize;
      method awvalid = s_axi.awvalid;
      method bid = s_axi.bid;
      method bready = s_axi.bready;
      method bresp = s_axi.bresp;
      method bvalid = s_axi.bvalid;
      method rdata = s_axi.rdata;
      method rlast = s_axi.rlast;
      method rready = s_axi.rready;
      method rresp = s_axi.rresp;
      method rvalid = s_axi.rvalid;
      method wdata = s_axi.wdata;
      method Action wid(Bit#(6) tag); /* no wid method */ endmethod
      method wlast = s_axi.wlast;
      method wready = s_axi.wready;
      method wstrb = s_axi.wstrb;
      method wvalid = s_axi.wvalid;
      method rid = s_axi.rid;
      interface Empty extra; endinterface
      endinterface);
endfunction

(* synthesize *)
module mkDdr3#(Clock clk200)(Ddr3);
   let clock <- exposeCurrentClock();
   let reset <- exposeCurrentReset();

   Reset rst200 <- mkAsyncReset( 10, reset, clk200 );

`ifndef BSV_POSITIVE_RESET
   let mcReset = rst200;
`else
   // fixme later
`endif

   //fixme mc.aresetn

   AxiDdr3     mc <- mkAxiDdr3(clk200, mcReset);
   let ui_reset_n <- mkResetInverter(mc.ui_clk_sync_rst, clocked_by mc.ui_clk);
   let axiBits = toAxiSlaveBits(mc.s_axi);
   Axi4SlaveCommon#(Ddr3AddrWidth,Ddr3DataWidth,6,Empty) axiSlaveCommon <- mkAxi4SlaveGather(axiBits, clocked_by mc.ui_clk, reset_by ui_reset_n);

   rule misc_pins;
      mc.app.ref_req(0);
      mc.app.sr_req(0);
      mc.app.zq_req(0);
   endrule

   interface slave = axiSlaveCommon.server;
   interface Ddr3Pins ddr3;
      interface AxiDdr3Ddr3 ddr3 = mc.ddr3;
      interface Clock sysclk_deleteme_unused_clock = clock; //fixme
      interface Reset sysrst_deleteme_unused_reset = reset; //fixme
   endinterface
   interface Clock uiClock = mc.ui_clk;
   interface Reset uiReset = ui_reset_n;
endmodule

export Ddr3AddrWidth;
export Ddr3DataWidth;
export Ddr3Pins(..);
export Ddr3(..);
export AxiDdr3Ddr3(..);
export mkDdr3;

// Copyright (c) 2015 Connectal Project

// 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.

import Clocks          ::*;
import GetPut          ::*;
import ClientServer    ::*;
import AxiDdr3Wrapper  ::*;
import AxiBits         ::*;
import AxiGather       ::*;
import Axi4MasterSlave ::*;
import ConnectalClocks ::*;
`include "ConnectalProjectConfig.bsv"

typedef 30 Ddr3AddrWidth;
typedef 512 Ddr3DataWidth;

interface Ddr3;
   interface Axi4Slave#(Ddr3AddrWidth,Ddr3DataWidth,6) slave;
   interface AxiDdr3Ddr3 ddr3; // pins
   interface Clock uiClock;
   interface Reset uiReset;
endinterface

function Axi4SlaveBits#(Ddr3AddrWidth,Ddr3DataWidth,6,Empty) toAxiSlaveBits(AxiDdr3S_axi s_axi);
   return (interface Axi4SlaveBits;
      method araddr = s_axi.araddr;
      method arburst = s_axi.arburst;
      method arcache = s_axi.arcache;
      method Bit#(1) aresetn(); return 1; endmethod
      method arid = s_axi.arid;
      method arlen = s_axi.arlen;
      method Action arlock(Bit#(2) l); s_axi.arlock(truncate(l)); endmethod //unused
      method arprot = s_axi.arprot;
      method arqos = s_axi.arqos;
      method arready = s_axi.arready;
      method arsize = s_axi.arsize;
      method arvalid = s_axi.arvalid;
      method awaddr = s_axi.awaddr;
      method awburst = s_axi.awburst;
      method awcache = s_axi.awcache;
      method awid = s_axi.awid;
      method awlen = s_axi.awlen;
      method Action awlock(Bit#(2) l); s_axi.awlock(truncate(l)); endmethod //unused
      method awprot = s_axi.awprot;
      method awqos = s_axi.awqos;
      method awready = s_axi.awready;
      method awsize = s_axi.awsize;
      method awvalid = s_axi.awvalid;
      method bid = s_axi.bid;
      method bready = s_axi.bready;
      method bresp = s_axi.bresp;
      method bvalid = s_axi.bvalid;
      method rdata = s_axi.rdata;
      method rlast = s_axi.rlast;
      method rready = s_axi.rready;
      method rresp = s_axi.rresp;
      method rvalid = s_axi.rvalid;
      method wdata = s_axi.wdata;
      method Action wid(Bit#(6) tag); /* no wid method */ endmethod
      method wlast = s_axi.wlast;
      method wready = s_axi.wready;
      method wstrb = s_axi.wstrb;
      method wvalid = s_axi.wvalid;
      method rid = s_axi.rid;
      interface Empty extra; endinterface
      endinterface);
endfunction

(* synthesize *)
module mkDdr3#(Clock ddr3Clock)(Ddr3);
   let clock <- exposeCurrentClock();
   let reset <- exposeCurrentReset();
`ifndef BSV_POSITIVE_RESET
   let positiveReset <- mkPositiveReset(10, reset, clock);
   let mcReset = positiveReset.positiveReset;
`else
   let mcReset = reset;
`endif

   //fixme mc.aresetn

   AxiDdr3     mc <- mkAxiDdr3(clock, mcReset, clock); // fixme clocks
   let ui_reset_n <- mkResetInverter(mc.ui_clk_sync_rst, clocked_by mc.ui_clk);
   let axiBits = toAxiSlaveBits(mc.s_axi);
   Axi4SlaveCommon#(Ddr3AddrWidth,Ddr3DataWidth,6,Empty) axiSlaveCommon <- mkAxi4SlaveGather(axiBits, clocked_by mc.ui_clk, reset_by ui_reset_n);

   rule misc_pins;
      mc.app.ref_req(0);
      mc.app.sr_req(0);
      mc.app.zq_req(0);
   endrule

   interface slave = axiSlaveCommon.server;
   interface ddr3 = mc.ddr3;
   interface Clock uiClock = mc.ui_clk;
   interface Reset uiReset = ui_reset_n;
endmodule