// File: NishadSaraf[P]Championship-Simulator/src/dram_controller.cc
// Function: MEMORY_CONTROLLER::process(PACKET_QUEUE*)
void MEMORY_CONTROLLER::process(PACKET_QUEUE *queue)
{
    uint32_t request_index = queue->next_process_index;

    // sanity check
    if (request_index == queue->SIZE)
        assert(0);

    uint8_t  op_type = queue->entry[request_index].type;
    uint64_t op_addr = queue->entry[request_index].address;
    uint32_t op_cpu = queue->entry[request_index].cpu,
             op_channel = dram_get_channel(op_addr), 
             op_rank = dram_get_rank(op_addr), 
             op_bank = dram_get_bank(op_addr);
#ifdef DEBUG_PRINT
    uint32_t op_row = dram_get_row(op_addr), 
             op_column = dram_get_column(op_addr);
#endif

    // sanity check
    if (bank_request[op_channel][op_rank][op_bank].request_index != (int)request_index) {
        assert(0);
    }

    // paid all DRAM access latency, data is ready to be processed
    if (bank_request[op_channel][op_rank][op_bank].cycle_available <= current_core_cycle[op_cpu]) {

        // check if data bus is available
        if (dbus_cycle_available[op_channel] <= current_core_cycle[op_cpu]) {

            if (queue->is_WQ) {
                // update data bus cycle time
                dbus_cycle_available[op_channel] = current_core_cycle[op_cpu] + DRAM_DBUS_RETURN_TIME;

                if (bank_request[op_channel][op_rank][op_bank].row_buffer_hit)
                    queue->ROW_BUFFER_HIT++;
                else
                    queue->ROW_BUFFER_MISS++;

                // this bank is ready for another DRAM request
                bank_request[op_channel][op_rank][op_bank].request_index = -1;
                bank_request[op_channel][op_rank][op_bank].row_buffer_hit = 0;
                bank_request[op_channel][op_rank][op_bank].working = false;
                bank_request[op_channel][op_rank][op_bank].is_write = 0;
                bank_request[op_channel][op_rank][op_bank].is_read = 0;

                scheduled_writes--;
            } else {
#ifdef DRAM_CACHE
                // DRAM cannot return data if DRC WQ is full
                if (upper_level_dcache[op_cpu]->get_occupancy(2) == upper_level_dcache[op_cpu]->get_size(2))
                    return;
#endif
                // update data bus cycle time
                dbus_cycle_available[op_channel] = current_core_cycle[op_cpu] + DRAM_DBUS_RETURN_TIME;
                queue->entry[request_index].event_cycle = dbus_cycle_available[op_channel]; 

                DP ( if (warmup_complete[op_cpu]) {
                cout << "[" << queue->NAME << "] " <<  __func__ << " return data" << hex;
                cout << " address: " << queue->entry[request_index].address << " full_addr: " << queue->entry[request_index].full_addr << dec;
                cout << " occupancy: " << queue->occupancy << " channel: " << op_channel << " rank: " << op_rank << " bank: " << op_bank;
                cout << " row: " << op_row << " column: " << op_column;
                cout << " current_cycle: " << current_core_cycle[op_cpu] << " event_cycle: " << queue->entry[request_index].event_cycle << endl; });

                // send data back to the core cache hierarchy
                upper_level_dcache[op_cpu]->return_data(&queue->entry[request_index]);

                if (bank_request[op_channel][op_rank][op_bank].row_buffer_hit)
                    queue->ROW_BUFFER_HIT++;
                else
                    queue->ROW_BUFFER_MISS++;

                // this bank is ready for another DRAM request
                bank_request[op_channel][op_rank][op_bank].request_index = -1;
                bank_request[op_channel][op_rank][op_bank].row_buffer_hit = 0;
                bank_request[op_channel][op_rank][op_bank].working = false;
                bank_request[op_channel][op_rank][op_bank].is_write = 0;
                bank_request[op_channel][op_rank][op_bank].is_read = 0;

                scheduled_reads--;
            }

            // remove the oldest entry
            queue->remove_queue(&queue->entry[request_index]);
            update_process_cycle(queue);
        }
        else { // data bus is busy, the available bank cycle time is fast-forwarded for faster simulation

#if 0
            // TODO: what if we can service prefetching request without dbus congestion?
            // can we have more timely prefetches and improve performance?
            if ((op_type == PREFETCH) || (op_type == LOAD)) {
                // just magically return prefetch request (no need to update data bus cycle time)
                /*
                dbus_cycle_available[op_channel] = current_core_cycle[op_cpu] + DRAM_DBUS_RETURN_TIME;
                queue->entry[request_index].event_cycle = dbus_cycle_available[op_channel]; 

                DP ( if (warmup_complete[op_cpu]) {
                cout << "[" << queue->NAME << "] " <<  __func__ << " return data" << hex;
                cout << " address: " << queue->entry[request_index].address << " full_addr: " << queue->entry[request_index].full_addr << dec;
                cout << " occupancy: " << queue->occupancy << " channel: " << op_channel << " rank: " << op_rank << " bank: " << op_bank;
                cout << " row: " << op_row << " column: " << op_column;
                cout << " current_cycle: " << current_core_cycle[op_cpu] << " event_cycle: " << queue->entry[request_index].event_cycle << endl; });
                */

                // send data back to the core cache hierarchy
                upper_level_dcache[op_cpu]->return_data(&queue->entry[request_index]);

                if (bank_request[op_channel][op_rank][op_bank].row_buffer_hit)
                    queue->ROW_BUFFER_HIT++;
                else
                    queue->ROW_BUFFER_MISS++;

                // this bank is ready for another DRAM request
                bank_request[op_channel][op_rank][op_bank].request_index = -1;
                bank_request[op_channel][op_rank][op_bank].row_buffer_hit = 0;
                bank_request[op_channel][op_rank][op_bank].working = false;
                bank_request[op_channel][op_rank][op_bank].is_write = 0;
                bank_request[op_channel][op_rank][op_bank].is_read = 0;

                scheduled_reads--;

                // remove the oldest entry
                queue->remove_queue(&queue->entry[request_index]);
                update_process_cycle(queue);

                return;
            }
#endif

            dbus_cycle_congested[op_channel] += (dbus_cycle_available[op_channel] - current_core_cycle[op_cpu]);
            bank_request[op_channel][op_rank][op_bank].cycle_available = dbus_cycle_available[op_channel];
            dbus_congested[NUM_TYPES][NUM_TYPES]++;
            dbus_congested[NUM_TYPES][op_type]++;
            dbus_congested[bank_request[op_channel][op_rank][op_bank].working_type][NUM_TYPES]++;
            dbus_congested[bank_request[op_channel][op_rank][op_bank].working_type][op_type]++;

            DP ( if (warmup_complete[op_cpu]) {
            cout << "[" << queue->NAME << "] " <<  __func__ << " dbus_occupied" << hex;
            cout << " address: " << queue->entry[request_index].address << " full_addr: " << queue->entry[request_index].full_addr << dec;
            cout << " occupancy: " << queue->occupancy << " channel: " << op_channel << " rank: " << op_rank << " bank: " << op_bank;
            cout << " row: " << op_row << " column: " << op_column;
            cout << " current_cycle: " << current_core_cycle[op_cpu] << " event_cycle: " << bank_request[op_channel][op_rank][op_bank].cycle_available << endl; });
        }
    }

    // TODO: Maybe we can handle the second oldest request something like that? 
    // If the bank is busy.. Was there some kind of job before? (Check DRAM scheduling paper)
    // How many times do we stuck by "waited enough but bank is still busy" scenario?

    // TODO: sometime channel is idel since tCAS takes 18 cycles and data transfer across channel takes only 16 cycles
    // if we use a wider channel the channel idle problem becomes more critical
}

// Assembly:
subq $0x38, %rsp
movq %rdi, 0x30(%rsp)
movq %rsi, 0x28(%rsp)
movq 0x30(%rsp), %rax
movq %rax, (%rsp)
movq 0x28(%rsp), %rax
movl 0x44(%rax), %eax
movl %eax, 0x24(%rsp)
movl 0x24(%rsp), %eax
movq 0x28(%rsp), %rcx
cmpl 0x20(%rcx), %eax
jne 0x141d0
leaq 0xee4c(%rip), %rdi      # 0x23004
leaq 0xf8a2(%rip), %rsi      # 0x23a61
movl $0x124, %edx            # imm = 0x124
leaq 0xf8eb(%rip), %rcx      # 0x23ab6
callq 0x3160
movq (%rsp), %rdi
movq 0x28(%rsp), %rax
movq 0x98(%rax), %rax
movl 0x24(%rsp), %ecx
imulq $0x380, %rcx, %rcx      # imm = 0x380
addq %rcx, %rax
movb 0x32f(%rax), %al
movb %al, 0x23(%rsp)
movq 0x28(%rsp), %rax
movq 0x98(%rax), %rax
movl 0x24(%rsp), %ecx
imulq $0x380, %rcx, %rcx      # imm = 0x380
addq %rcx, %rax
movq 0x340(%rax), %rax
movq %rax, 0x18(%rsp)
movq 0x28(%rsp), %rax
movq 0x98(%rax), %rax
movl 0x24(%rsp), %ecx
imulq $0x380, %rcx, %rcx      # imm = 0x380
addq %rcx, %rax
movl 0x330(%rax), %eax
movl %eax, 0x14(%rsp)
movq 0x18(%rsp), %rsi
callq 0x13490
movq (%rsp), %rdi
movl %eax, 0x10(%rsp)
movq 0x18(%rsp), %rsi
callq 0x134a0
movq (%rsp), %rdi
movl %eax, 0xc(%rsp)
movq 0x18(%rsp), %rsi
callq 0x134d0
movl %eax, %ecx
movq (%rsp), %rax
movl %ecx, 0x8(%rsp)
addq $0x7858, %rax           # imm = 0x7858
movl 0x10(%rsp), %ecx
imulq $0xa00, %rcx, %rcx      # imm = 0xA00
addq %rcx, %rax
movl 0xc(%rsp), %ecx
imulq $0x140, %rcx, %rcx      # imm = 0x140
addq %rcx, %rax
movl 0x8(%rsp), %ecx
imulq $0x28, %rcx, %rcx
addq %rcx, %rax
movl 0x24(%rax), %eax
cmpl 0x24(%rsp), %eax
je 0x142cf
leaq 0xed4d(%rip), %rdi      # 0x23004
leaq 0xf7a3(%rip), %rsi      # 0x23a61
movl $0x133, %edx            # imm = 0x133
leaq 0xf7ec(%rip), %rcx      # 0x23ab6
callq 0x3160
movq (%rsp), %rax
addq $0x7858, %rax           # imm = 0x7858
movl 0x10(%rsp), %ecx
imulq $0xa00, %rcx, %rcx      # imm = 0xA00
addq %rcx, %rax
movl 0xc(%rsp), %ecx
imulq $0x140, %rcx, %rcx      # imm = 0x140
addq %rcx, %rax
movl 0x8(%rsp), %ecx
imulq $0x28, %rcx, %rcx
addq %rcx, %rax
movq (%rax), %rax
movl 0x14(%rsp), %ecx
movl %ecx, %edx
leaq 0x7bbb0(%rip), %rcx     # 0x8fec0
cmpq (%rcx,%rdx,8), %rax
ja 0x14883
movq (%rsp), %rax
movl 0x10(%rsp), %ecx
movq 0x7568(%rax,%rcx,8), %rax
movl 0x14(%rsp), %ecx
movl %ecx, %edx
leaq 0x7bb89(%rip), %rcx     # 0x8fec0
cmpq (%rcx,%rdx,8), %rax
ja 0x1473e
movq 0x28(%rsp), %rax
cmpb $0x0, 0x25(%rax)
je 0x14501
movq (%rsp), %rax
movl 0x14(%rsp), %ecx
movl %ecx, %edx
leaq 0x7bb5f(%rip), %rcx     # 0x8fec0
movq (%rcx,%rdx,8), %rdx
movl 0x16ea5(%rip), %ecx     # 0x2b210
addq %rcx, %rdx
movl 0x10(%rsp), %ecx
movq %rdx, 0x7568(%rax,%rcx,8)
addq $0x7858, %rax           # imm = 0x7858
movl 0x10(%rsp), %ecx
imulq $0xa00, %rcx, %rcx      # imm = 0xA00
addq %rcx, %rax
movl 0xc(%rsp), %ecx
imulq $0x140, %rcx, %rcx      # imm = 0x140
addq %rcx, %rax
movl 0x8(%rsp), %ecx
imulq $0x28, %rcx, %rcx
addq %rcx, %rax
cmpb $0x0, 0x1e(%rax)
je 0x143c6
movq 0x28(%rsp), %rax
movq 0x80(%rax), %rcx
addq $0x1, %rcx
movq %rcx, 0x80(%rax)
jmp 0x143dd
movq 0x28(%rsp), %rax
movq 0x88(%rax), %rcx
addq $0x1, %rcx
movq %rcx, 0x88(%rax)
movq (%rsp), %rax
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movl $0xffffffff, 0x24(%rcx) # imm = 0xFFFFFFFF
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movb $0x0, 0x1e(%rcx)
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movb $0x0, 0x1c(%rcx)
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movb $0x0, 0x20(%rcx)
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movb $0x0, 0x21(%rcx)
movl 0x784c(%rax), %ecx
addl $-0x1, %ecx
movl %ecx, 0x784c(%rax)
jmp 0x14707
movq (%rsp), %rax
movl 0x14(%rsp), %ecx
movl %ecx, %edx
leaq 0x7b9ae(%rip), %rcx     # 0x8fec0
movq (%rcx,%rdx,8), %rdx
movl 0x16cf4(%rip), %ecx     # 0x2b210
addq %rcx, %rdx
movl 0x10(%rsp), %ecx
movq %rdx, 0x7568(%rax,%rcx,8)
movl 0x10(%rsp), %ecx
movq 0x7568(%rax,%rcx,8), %rdx
movq 0x28(%rsp), %rcx
movq 0x98(%rcx), %rcx
movl 0x24(%rsp), %esi
imulq $0x380, %rsi, %rsi      # imm = 0x380
addq %rsi, %rcx
movq %rdx, 0x378(%rcx)
movl 0x14(%rsp), %ecx
movq 0x10(%rax,%rcx,8), %rdi
movq 0x28(%rsp), %rax
movq 0x98(%rax), %rsi
movl 0x24(%rsp), %eax
imulq $0x380, %rax, %rax      # imm = 0x380
addq %rax, %rsi
movq (%rdi), %rax
callq *0x18(%rax)
movq (%rsp), %rax
addq $0x7858, %rax           # imm = 0x7858
movl 0x10(%rsp), %ecx
imulq $0xa00, %rcx, %rcx      # imm = 0xA00
addq %rcx, %rax
movl 0xc(%rsp), %ecx
imulq $0x140, %rcx, %rcx      # imm = 0x140
addq %rcx, %rax
movl 0x8(%rsp), %ecx
imulq $0x28, %rcx, %rcx
addq %rcx, %rax
cmpb $0x0, 0x1e(%rax)
je 0x145d1
movq 0x28(%rsp), %rax
movq 0x80(%rax), %rcx
addq $0x1, %rcx
movq %rcx, 0x80(%rax)
jmp 0x145e8
movq 0x28(%rsp), %rax
movq 0x88(%rax), %rcx
addq $0x1, %rcx
movq %rcx, 0x88(%rax)
movq (%rsp), %rax
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movl $0xffffffff, 0x24(%rcx) # imm = 0xFFFFFFFF
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movb $0x0, 0x1e(%rcx)
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movb $0x0, 0x1c(%rcx)
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movb $0x0, 0x20(%rcx)
movq %rax, %rcx
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movb $0x0, 0x21(%rcx)
movl 0x7848(%rax), %ecx
addl $-0x1, %ecx
movl %ecx, 0x7848(%rax)
movq 0x28(%rsp), %rdi
movq 0x28(%rsp), %rax
movq 0x98(%rax), %rsi
movl 0x24(%rsp), %eax
imulq $0x380, %rax, %rax      # imm = 0x380
addq %rax, %rsi
callq 0x150e0
movq (%rsp), %rdi
movq 0x28(%rsp), %rsi
callq 0x13650
jmp 0x14881
movq (%rsp), %rcx
movl 0x10(%rsp), %eax
movq 0x7568(%rcx,%rax,8), %rdx
movl 0x14(%rsp), %eax
movl %eax, %esi
leaq 0x7b765(%rip), %rax     # 0x8fec0
subq (%rax,%rsi,8), %rdx
movl 0x10(%rsp), %eax
addq 0x7570(%rcx,%rax,8), %rdx
movq %rdx, 0x7570(%rcx,%rax,8)
movl 0x10(%rsp), %eax
movq 0x7568(%rcx,%rax,8), %rdx
movq %rcx, %rax
addq $0x7858, %rax           # imm = 0x7858
movl 0x10(%rsp), %esi
imulq $0xa00, %rsi, %rsi      # imm = 0xA00
addq %rsi, %rax
movl 0xc(%rsp), %esi
imulq $0x140, %rsi, %rsi      # imm = 0x140
addq %rsi, %rax
movl 0x8(%rsp), %esi
imulq $0x28, %rsi, %rsi
addq %rsi, %rax
movq %rdx, (%rax)
movq 0x7638(%rcx), %rax
addq $0x1, %rax
movq %rax, 0x7638(%rcx)
movzbl 0x23(%rsp), %eax
movq 0x7618(%rcx,%rax,8), %rdx
addq $0x1, %rdx
movq %rdx, 0x7618(%rcx,%rax,8)
movq %rcx, %rax
addq $0x7578, %rax           # imm = 0x7578
movq %rcx, %rdx
addq $0x7858, %rdx           # imm = 0x7858
movl 0x10(%rsp), %esi
imulq $0xa00, %rsi, %rsi      # imm = 0xA00
addq %rsi, %rdx
movl 0xc(%rsp), %esi
imulq $0x140, %rsi, %rsi      # imm = 0x140
addq %rsi, %rdx
movl 0x8(%rsp), %esi
imulq $0x28, %rsi, %rsi
addq %rsi, %rdx
movzbl 0x1d(%rdx), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rax
movq 0x20(%rax), %rdx
addq $0x1, %rdx
movq %rdx, 0x20(%rax)
movq %rcx, %rax
addq $0x7578, %rax           # imm = 0x7578
addq $0x7858, %rcx           # imm = 0x7858
movl 0x10(%rsp), %edx
imulq $0xa00, %rdx, %rdx      # imm = 0xA00
addq %rdx, %rcx
movl 0xc(%rsp), %edx
imulq $0x140, %rdx, %rdx      # imm = 0x140
addq %rdx, %rcx
movl 0x8(%rsp), %edx
imulq $0x28, %rdx, %rdx
addq %rdx, %rcx
movzbl 0x1d(%rcx), %ecx
imulq $0x28, %rcx, %rcx
addq %rcx, %rax
movzbl 0x23(%rsp), %ecx
movq (%rax,%rcx,8), %rdx
addq $0x1, %rdx
movq %rdx, (%rax,%rcx,8)
jmp 0x14883
addq $0x38, %rsp
retq
nopl (%rax,%rax)