692 lines
25 KiB
Plaintext
692 lines
25 KiB
Plaintext
// Copyright (c) 2016-2019 Bluespec, Inc. All Rights Reserved
|
|
//
|
|
//-
|
|
// RVFI_DII + CHERI modifications:
|
|
// Copyright (c) 2020 Alexandre Joannou
|
|
// All rights reserved.
|
|
//
|
|
// This software was developed by SRI International and the University of
|
|
// Cambridge Computer Laboratory (Department of Computer Science and
|
|
// Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the
|
|
// DARPA SSITH research programme.
|
|
//
|
|
// This work was supported by NCSC programme grant 4212611/RFA 15971 ("SafeBet").
|
|
//-
|
|
|
|
package Mem_Controller;
|
|
|
|
// ================================================================
|
|
// This module is a slave on the interconnect Fabric.
|
|
//
|
|
// On the back side of the Mem_Controller is a ``raw'' memory
|
|
// interface, a simple, wide, R/W interface,
|
|
// which is connected to real memory in hardware (BRAM, DRAM, ...)
|
|
// and to a model thereof in simulation.
|
|
//
|
|
// The raw mem interface data width is typically one or two cache lines.
|
|
// Note: raw mem write requests are 'fire and forget'; there is no ack
|
|
|
|
// ----------------
|
|
// This slave IP can be attached to fabrics with 32b- or 64b-wide data channels.
|
|
// (NOTE: this is the width of the fabric, which can be chosen
|
|
// independently of the native width of a CPU master on the
|
|
// fabric (such as RV32/RV64 for a RISC-V CPU).
|
|
// When attached to 32b-wide fabric, 64-bit locations must be
|
|
// read/written in two 32b transaction, once for the lower 32b and
|
|
// once for the upper 32b.
|
|
|
|
// When fabric data is 64b wide, fabric addresses must be 8B-aligned
|
|
// - Reads always return 64b data
|
|
// - Write data should be 64b wide with an 8b byte-strobe indicating
|
|
// which bytes are to be written. Strobes should be for aligned
|
|
// 1B, 2B, 4B or 8B chunks.
|
|
|
|
// When fabric data is 32b wide, fabric addresses must be 4B-aligned
|
|
// - Reads always return 32b data
|
|
// - Write data should be 32b wide with a 4b byte-strobe indicating
|
|
// which bytes are to be written. Strobes should for aligned
|
|
// 1B, 2B, or 4B chunks.
|
|
|
|
// Some of the 'truncate()'s and 'zeroExtend()'s below are no-ops but
|
|
// necessary to satisfy type-checking.
|
|
// ================================================================
|
|
|
|
export
|
|
|
|
Bits_per_Raw_Mem_Addr,
|
|
Raw_Mem_Addr,
|
|
|
|
Bits_per_Raw_Mem_Word,
|
|
Raw_Mem_Word,
|
|
|
|
Mem_Controller_IFC (..),
|
|
mkMem_Controller,
|
|
|
|
status_mem_controller_terminated;
|
|
|
|
// ================================================================
|
|
// BSV library imports
|
|
|
|
import Vector :: *;
|
|
import FIFOF :: *;
|
|
import SpecialFIFOs :: *;
|
|
import GetPut :: *;
|
|
import ClientServer :: *;
|
|
import Memory :: *;
|
|
import ConfigReg :: *;
|
|
|
|
// ----------------
|
|
// BSV additional libs
|
|
|
|
import Cur_Cycle :: *;
|
|
import GetPut_Aux :: *;
|
|
import Semi_FIFOF :: *;
|
|
import ByteLane :: *;
|
|
import AXI4 :: *;
|
|
import SourceSink :: *;
|
|
|
|
// ================================================================
|
|
// Project imports
|
|
|
|
import Fabric_Defs :: *;
|
|
import SoC_Map :: *;
|
|
|
|
// ================================================================
|
|
// Raw mem data width: 256 (bits/ 32 x Byte/ 8 x Word32/ 4 x Word64)
|
|
// Raw mem address width: 64 (arbitrarily chosen generously large)
|
|
|
|
typedef 256 Bits_per_Raw_Mem_Word;
|
|
typedef Bit #(Bits_per_Raw_Mem_Word) Raw_Mem_Word;
|
|
|
|
typedef 64 Bits_per_Raw_Mem_Addr;
|
|
typedef Bit #(Bits_per_Raw_Mem_Addr) Raw_Mem_Addr;
|
|
|
|
// ----------------
|
|
// Views of raw mem word as bytes, word32s and word64s
|
|
// Example values on the right based on 256b raw mem data width
|
|
typedef TDiv #(Bits_per_Raw_Mem_Word, 8) Bytes_per_Raw_Mem_Word; // 32 bytes
|
|
Integer bytes_per_raw_mem_word = valueOf (Bytes_per_Raw_Mem_Word);
|
|
|
|
// # of addr lsbs to index a byte in a Raw_Mem_Word
|
|
typedef TLog #(Bytes_per_Raw_Mem_Word) Bits_per_Byte_in_Raw_Mem_Word; // 5
|
|
Integer bits_per_byte_in_raw_mem_word = valueOf (Bits_per_Byte_in_Raw_Mem_Word);
|
|
Integer hi_byte_in_raw_mem_word = bits_per_byte_in_raw_mem_word - 1; // 4
|
|
|
|
typedef TDiv #(Bits_per_Raw_Mem_Word, 32) Word32s_per_Raw_Mem_Word; // 8 x 32b words
|
|
Integer word32s_per_raw_mem_word = valueOf (Word32s_per_Raw_Mem_Word);
|
|
// # of addr lsbs to index a Word32 in a Raw_Mem_Word seen as a vector of Word32s
|
|
typedef TLog #(Word32s_per_Raw_Mem_Word) Bits_per_Word32_in_Raw_Mem_Word; // 3
|
|
// Type of index of a Word32 in a Raw_Mem_Word seen as a vector of Word32s
|
|
typedef Bit #(Bits_per_Word32_in_Raw_Mem_Word) Word32_in_Raw_Mem_Word;
|
|
|
|
typedef TDiv #(Bits_per_Raw_Mem_Word, 64) Word64s_per_Raw_Mem_Word; // 4 x 64b words
|
|
Integer word64s_per_raw_mem_word = valueOf (Word64s_per_Raw_Mem_Word);
|
|
// # of addr lsbs to index a Word64 in a Raw_Mem_Word seen as a vector of Word64s
|
|
typedef TLog #(Word64s_per_Raw_Mem_Word) Bits_per_Word64_in_Raw_Mem_Word; // 2
|
|
// Type of index of a Word64 in a Raw_Mem_Word seen as a vector of Word64s
|
|
typedef Bit #(Bits_per_Word64_in_Raw_Mem_Word) Word64_in_Raw_Mem_Word;
|
|
|
|
typedef TDiv #(Bytes_per_Raw_Mem_Word, Bytes_per_Fabric_Data) Fabric_Data_per_Raw_Mem_Word;
|
|
|
|
// Index of bit that selects a fabric data word in an address
|
|
`ifdef FABRIC32
|
|
Integer lo_fabric_data = 2;
|
|
`endif
|
|
|
|
`ifdef FABRIC64
|
|
Integer lo_fabric_data = 3;
|
|
`endif
|
|
|
|
// ================================================================
|
|
|
|
function Bool fn_addr_is_aligned (Fabric_Addr addr, AXI4_Size size);
|
|
Bool is_aligned = ( (size == 1)
|
|
|| ((size == 2) && (addr [0] == 1'h0))
|
|
|| ((size == 4) && (addr [1:0] == 2'h0))
|
|
|| ((size == 8) && (addr [2:0] == 3'h0))
|
|
|| ((size == 16) && (addr [3:0] == 4'h0))
|
|
|| ((size == 32) && (addr [4:0] == 5'h0))
|
|
|| ((size == 64) && (addr [5:0] == 6'h0))
|
|
|| ((size == 128) && (addr [6:0] == 7'h0)));
|
|
return is_aligned;
|
|
endfunction
|
|
|
|
function Bool fn_addr_is_in_range (Fabric_Addr addr_base, Fabric_Addr addr, Fabric_Addr addr_lim);
|
|
// Note: 'in_range' is redundant if the fabric only delivers
|
|
// relevant addresses to this module so this is just a bit of
|
|
// defensive programming.
|
|
|
|
return ((addr_base <= addr) && (addr < addr_lim));
|
|
endfunction
|
|
|
|
function Bool fn_addr_is_ok (Fabric_Addr addr_base, Fabric_Addr addr, Fabric_Addr addr_lim, AXI4_Size size);
|
|
return ( fn_addr_is_aligned (addr, size)
|
|
&& fn_addr_is_in_range (addr_base, addr, addr_lim));
|
|
endfunction
|
|
|
|
// Compute raw mem addr that holds a given fabric addr
|
|
function Raw_Mem_Addr fn_addr_to_raw_mem_addr (Fabric_Addr addr);
|
|
Fabric_Addr a1 = addr >> log2 (bytes_per_raw_mem_word);
|
|
return extend (a1);
|
|
endfunction
|
|
|
|
// Compute # of raw mem words from base to lim fabric addrs
|
|
function Raw_Mem_Addr fn_raw_mem_words_per_mem (Fabric_Addr base, Fabric_Addr lim);
|
|
return fn_addr_to_raw_mem_addr (lim - base);
|
|
endfunction
|
|
|
|
// ================================================================
|
|
// Local constants and types
|
|
|
|
// Module state
|
|
typedef enum {STATE_POWER_ON_RESET,
|
|
`ifdef INCLUDE_INITIAL_MEMZERO
|
|
STATE_ZEROING_MEM, // while zero-ing out memory
|
|
`endif
|
|
STATE_RESET_RELOAD_CACHE, // on reset, start reload on reset
|
|
STATE_RELOADING, // while reloading the raw-mem word cache
|
|
STATE_READY // while handling requests
|
|
} State
|
|
deriving (Bits, Eq, FShow);
|
|
|
|
// ================================================================
|
|
// Catch-all status
|
|
|
|
Integer status_mem_controller_terminated = 1;
|
|
|
|
// ================================================================
|
|
// Interface
|
|
|
|
typedef Wd_AW_User Wd_User;
|
|
export Wd_User;
|
|
interface Mem_Controller_IFC;
|
|
// Reset
|
|
interface Server #(Bit #(0), Bit #(0)) server_reset;
|
|
|
|
// set_addr_map should be called after this module's reset
|
|
method Action set_addr_map (Fabric_Addr addr_base, Fabric_Addr addr_lim);
|
|
|
|
// Main Fabric Reqs/Rsps
|
|
interface AXI4_Slave #(Wd_SId, Wd_Addr, Wd_Data, 0, 0, 0, 0, 0)
|
|
slave;
|
|
|
|
// To raw memory (outside the SoC)
|
|
interface MemoryClient #(Bits_per_Raw_Mem_Addr, Bits_per_Raw_Mem_Word) to_raw_mem;
|
|
|
|
// Catch-all status; return-value can identify the origin (0 = none)
|
|
(* always_ready *)
|
|
method Bit #(8) status;
|
|
|
|
// For ISA tests: watch memory writes to <tohost> addr
|
|
method Action set_watch_tohost (Bool watch_tohost, Fabric_Addr tohost_addr);
|
|
endinterface
|
|
|
|
// ================================================================
|
|
// AXI4 has independent read and write channels and does not specify
|
|
// which one should be prioritized if requests are available on both
|
|
// channels. We merge them into a single queue.
|
|
|
|
typedef enum { REQ_OP_RD, REQ_OP_WR } Req_Op
|
|
deriving (Bits, Eq, FShow);
|
|
|
|
typedef struct {Req_Op req_op;
|
|
|
|
// AW and AR channel info
|
|
Bit#(Wd_SId) id;
|
|
Fabric_Addr addr;
|
|
AXI4_Len len;
|
|
AXI4_Size size;
|
|
AXI4_Burst burst;
|
|
AXI4_Lock lock;
|
|
AXI4_Cache cache;
|
|
AXI4_Prot prot;
|
|
AXI4_QoS qos;
|
|
AXI4_Region region;
|
|
Bit #(Wd_User) user;
|
|
|
|
// Write data info
|
|
Bit #(TDiv #(Wd_Data, 8)) wstrb;
|
|
Fabric_Data data;
|
|
} Req
|
|
deriving (Bits, FShow);
|
|
|
|
// ================================================================
|
|
|
|
(* synthesize *)
|
|
module mkMem_Controller (Mem_Controller_IFC);
|
|
|
|
// verbosity 0: quiet
|
|
// verbosity 1: reset, initialized
|
|
// verbosity 2: reads, writes
|
|
// verbosity 3: more detail of local raw_mem interactions
|
|
Reg #(Bit #(4)) cfg_verbosity <- mkConfigReg (0);
|
|
|
|
Reg #(State) rg_state <- mkReg (STATE_POWER_ON_RESET);
|
|
Reg #(Fabric_Addr) rg_addr_base <- mkRegU;
|
|
Reg #(Fabric_Addr) rg_addr_lim <- mkRegU;
|
|
|
|
FIFOF #(Bit #(0)) f_reset_reqs <- mkFIFOF;
|
|
FIFOF #(Bit #(0)) f_reset_rsps <- mkFIFOF;
|
|
|
|
// Communication with fabric
|
|
let slavePortShim <- mkAXI4ShimFF;
|
|
|
|
// Requests merged from the (WrA, WrD) and RdA channels
|
|
FIFOF #(Req) f_reqs <- mkPipelineFIFOF;
|
|
|
|
// FIFOFs for requests/responses to raw memory
|
|
FIFOF #(MemoryRequest #(Bits_per_Raw_Mem_Addr, Bits_per_Raw_Mem_Word))
|
|
f_raw_mem_reqs <- mkPipelineFIFOF;
|
|
FIFOF #(MemoryResponse #(Bits_per_Raw_Mem_Word))
|
|
f_raw_mem_rsps <- mkPipelineFIFOF;
|
|
|
|
// We maintain a 1-raw_mem_word cache
|
|
Reg #(Bool) rg_cached_clean <- mkRegU;
|
|
Reg #(Raw_Mem_Addr) rg_cached_raw_mem_addr <- mkRegU;
|
|
Reg #(Raw_Mem_Word) rg_cached_raw_mem_word <- mkRegU;
|
|
|
|
// Ad hoc ISA-test simulation support: watch <tohost> and stop on non-zero write.
|
|
// The default tohost_addr here is fragile (may change on recompilation of tests).
|
|
// Proper value can be provided with 'set_watch_tohost' method from symbol table
|
|
Reg #(Bool) rg_watch_tohost <- mkReg (False);
|
|
Reg #(Fabric_Addr) rg_tohost_addr <- mkReg ('h_8000_1000);
|
|
|
|
// Catch-all status
|
|
Reg #(Bit #(8)) rg_status <- mkReg (0);
|
|
|
|
// ================================================================
|
|
// BEHAVIOR
|
|
|
|
// ----------------------------------------------------------------
|
|
// Reset
|
|
|
|
function Action fa_reset_actions;
|
|
action
|
|
slavePortShim.clear;
|
|
f_raw_mem_reqs.clear;
|
|
f_raw_mem_rsps.clear;
|
|
rg_status <= 0;
|
|
endaction
|
|
endfunction
|
|
|
|
rule rl_power_on_reset (rg_state == STATE_POWER_ON_RESET);
|
|
if (cfg_verbosity > 1)
|
|
$display ("%0d: Mem_Controller.rl_power_on_reset", cur_cycle);
|
|
fa_reset_actions ();
|
|
rg_state <= STATE_RESET_RELOAD_CACHE;
|
|
endrule
|
|
|
|
rule rl_external_reset (rg_state == STATE_READY);
|
|
if (cfg_verbosity > 1)
|
|
$display ("%0d: Mem_Controller.rl_external_reset => STATE_RESET_RELOAD_CACHE", cur_cycle);
|
|
|
|
f_reset_reqs.deq;
|
|
fa_reset_actions ();
|
|
rg_state <= STATE_RESET_RELOAD_CACHE;
|
|
f_reset_rsps.enq (?);
|
|
endrule
|
|
|
|
// On reset, we initialize the local cache with contents of raw_mem_addr 0
|
|
rule rl_reset_reload_cache (rg_state == STATE_RESET_RELOAD_CACHE);
|
|
let raw_mem_req = MemoryRequest {write: False,
|
|
byteen: '1,
|
|
address: 0,
|
|
data: ?};
|
|
f_raw_mem_reqs.enq (raw_mem_req);
|
|
rg_cached_raw_mem_addr <= 0;
|
|
rg_state <= STATE_RELOADING;
|
|
if (cfg_verbosity > 1)
|
|
$display ("%0d: Mem_Controller.rl_reset_reload_cache => STATE_RELOADING", cur_cycle);
|
|
endrule
|
|
|
|
// ----------------------------------------------------------------
|
|
// Merge requests into a single queue, prioritizing reads over writes
|
|
|
|
rule rl_merge_rd_req;
|
|
let rda <- get(slavePortShim.master.ar);
|
|
let req = Req {req_op: REQ_OP_RD,
|
|
id: rda.arid,
|
|
addr: rda.araddr,
|
|
len: rda.arlen,
|
|
size: rda.arsize,
|
|
burst: rda.arburst,
|
|
lock: rda.arlock,
|
|
cache: rda.arcache,
|
|
prot: rda.arprot,
|
|
qos: rda.arqos,
|
|
region: rda.arregion,
|
|
user: rda.aruser,
|
|
wstrb: ?,
|
|
data: ?};
|
|
f_reqs.enq (req);
|
|
|
|
if (cfg_verbosity > 2) begin
|
|
$display ("%0d: Mem_Controller.rl_merge_rd_req", cur_cycle);
|
|
$display (" ", fshow (rda));
|
|
end
|
|
endrule
|
|
|
|
(* descending_urgency = "rl_merge_rd_req, rl_merge_wr_req" *)
|
|
rule rl_merge_wr_req;
|
|
let wra <- get(slavePortShim.master.aw);
|
|
let wrd <- get(slavePortShim.master.w);
|
|
let req = Req {req_op: REQ_OP_WR,
|
|
id: wra.awid,
|
|
addr: wra.awaddr,
|
|
len: wra.awlen,
|
|
size: wra.awsize,
|
|
burst: wra.awburst,
|
|
lock: wra.awlock,
|
|
cache: wra.awcache,
|
|
prot: wra.awprot,
|
|
qos: wra.awqos,
|
|
region: wra.awregion,
|
|
user: wra.awuser,
|
|
wstrb: wrd.wstrb,
|
|
data: wrd.wdata};
|
|
f_reqs.enq (req);
|
|
|
|
if (cfg_verbosity > 2) begin
|
|
$display ("%0d: Mem_Controller.rl_merge_wr_req", cur_cycle);
|
|
$display (" ", fshow (wra));
|
|
$display (" ", fshow (wrd));
|
|
end
|
|
endrule
|
|
|
|
// ----------------------------------------------------------------
|
|
// Handle request from fabric
|
|
|
|
let req_byte_offset = f_reqs.first.addr - rg_addr_base; // within this memory unit
|
|
let req_raw_mem_addr = fn_addr_to_raw_mem_addr (req_byte_offset);
|
|
|
|
// ----------------
|
|
// This rule fires when there's no fabric req and the cached raw_mem_word is dirty;
|
|
// it writes back the dirty raw_mem_word; the cached raw_mem_word becomes clean
|
|
|
|
rule rl_writeback_dirty_idle ( (rg_state == STATE_READY)
|
|
&& (! f_reqs.notEmpty) // Idle
|
|
&& (! rg_cached_clean));
|
|
let raw_mem_req = MemoryRequest {write: True,
|
|
byteen: '1,
|
|
address: rg_cached_raw_mem_addr,
|
|
data: rg_cached_raw_mem_word};
|
|
f_raw_mem_reqs.enq (raw_mem_req);
|
|
rg_cached_clean <= True;
|
|
if (cfg_verbosity > 2)
|
|
$display ("%0d: Mem_Controller.rl_writeback_dirty_idle to raw addr 0x%0h",
|
|
cur_cycle, rg_cached_raw_mem_addr);
|
|
endrule
|
|
|
|
// ----------------
|
|
// This rule fires on a fabric req when the cached raw_mem_word has a
|
|
// different raw_mem_word-addr and is dirty;
|
|
// it writes back the dirty raw_mem_word; the cached raw_mem_word becomes clean
|
|
|
|
rule rl_writeback_dirty ( (rg_state == STATE_READY)
|
|
&& fn_addr_is_ok (rg_addr_base, f_reqs.first.addr, rg_addr_lim, f_reqs.first.size)
|
|
&& (rg_cached_raw_mem_addr != req_raw_mem_addr)
|
|
&& (! rg_cached_clean));
|
|
let raw_mem_req = MemoryRequest {write: True,
|
|
byteen: '1,
|
|
address: rg_cached_raw_mem_addr,
|
|
data: rg_cached_raw_mem_word};
|
|
f_raw_mem_reqs.enq (raw_mem_req);
|
|
rg_cached_clean <= True;
|
|
if (cfg_verbosity > 2)
|
|
$display ("%0d: Mem_Controller.rl_writeback_dirty to raw addr 0x%0h",
|
|
cur_cycle, rg_cached_raw_mem_addr);
|
|
endrule
|
|
|
|
// ----------------
|
|
// This rule fires on a fabric req when the cached raw_mem_word has a
|
|
// different addr and is clean; we overwrite with the correct raw_mem_word
|
|
// by reloading from memory; the new cached raw_mem_word is clean.
|
|
|
|
rule rl_miss_clean_req ( (rg_state == STATE_READY)
|
|
&& fn_addr_is_ok (rg_addr_base, f_reqs.first.addr, rg_addr_lim, f_reqs.first.size)
|
|
&& (rg_cached_raw_mem_addr != req_raw_mem_addr)
|
|
&& rg_cached_clean);
|
|
let raw_mem_req = MemoryRequest {write: False,
|
|
byteen: '1,
|
|
address: req_raw_mem_addr,
|
|
data: ?};
|
|
f_raw_mem_reqs.enq (raw_mem_req);
|
|
rg_cached_raw_mem_addr <= req_raw_mem_addr;
|
|
rg_state <= STATE_RELOADING;
|
|
|
|
if (cfg_verbosity > 2)
|
|
$display ("%0d: Mem_Controller.rl_miss_clean_req: read raw addr 0x%0h",
|
|
cur_cycle, req_raw_mem_addr);
|
|
endrule
|
|
|
|
rule rl_reload (rg_state == STATE_RELOADING);
|
|
let raw_mem_rsp <- pop (f_raw_mem_rsps);
|
|
Raw_Mem_Word raw_mem_word = unpack (raw_mem_rsp.data);
|
|
rg_cached_raw_mem_word <= raw_mem_word;
|
|
rg_state <= STATE_READY;
|
|
rg_cached_clean <= True;
|
|
|
|
if (cfg_verbosity > 2) begin
|
|
$display ("%0d: Mem_Controller.rl_reload: raw addr 0x%0h", cur_cycle, rg_cached_raw_mem_addr);
|
|
$display (" ", fshow (raw_mem_word));
|
|
end
|
|
endrule
|
|
|
|
// ----------------
|
|
// This rule fires on a fabric read request when the cached raw_mem_word has the
|
|
// same addr ('hit'), whether clean or dirty.
|
|
// Returns the full Wd_Data-wide word containing the byte specified by the address.
|
|
// i.e., we do not extract relevant bytes here, leaving that to the requestor.
|
|
|
|
rule rl_process_rd_req ( (rg_state == STATE_READY)
|
|
&& fn_addr_is_ok (rg_addr_base, f_reqs.first.addr, rg_addr_lim, f_reqs.first.size)
|
|
&& (rg_cached_raw_mem_addr == req_raw_mem_addr)
|
|
&& (f_reqs.first.req_op == REQ_OP_RD));
|
|
|
|
// ----------------
|
|
// We need to select the fabric data word from the raw mem word that contains the target address.
|
|
|
|
// View the raw mem word as a vector of fabric data words (Wd_Data width words)
|
|
Vector #(Fabric_Data_per_Raw_Mem_Word, Bit #(Wd_Data)) raw_mem_word_V_fabric_data = unpack (rg_cached_raw_mem_word);
|
|
|
|
// Get the index into this vector of the fabric word containing the target address.
|
|
// For this index, use a generous size (here Bit #(16)), and let zeroExtend pad it automaticallly.
|
|
Fabric_Addr addr = f_reqs.first.addr;
|
|
Bit #(Bits_per_Byte_in_Raw_Mem_Word) n = addr [hi_byte_in_raw_mem_word : 0];
|
|
n = (n >> lo_fabric_data);
|
|
|
|
// Select the fabric data word of interest
|
|
Bit #(Wd_Data) rdata = raw_mem_word_V_fabric_data [n];
|
|
|
|
let rdr = AXI4_RFlit {rid: f_reqs.first.id,
|
|
rdata: rdata,
|
|
rresp: OKAY,
|
|
rlast: True,
|
|
ruser: 0'b0};
|
|
slavePortShim.master.r.put(rdr);
|
|
f_reqs.deq;
|
|
|
|
if (cfg_verbosity > 1) begin
|
|
$display ("%0d: Mem_Controller.rl_process_rd_req: ", cur_cycle);
|
|
$display (" ", fshow (f_reqs.first));
|
|
$display (" => ", fshow (rdr));
|
|
end
|
|
endrule
|
|
|
|
// ----------------
|
|
// This rule fires on a fabric write request when the cached raw_mem_word has the
|
|
// same addr ('hit'), whether clean or dirty.
|
|
|
|
rule rl_process_wr_req ( (rg_state == STATE_READY)
|
|
&& fn_addr_is_ok (rg_addr_base, f_reqs.first.addr, rg_addr_lim, f_reqs.first.size)
|
|
&& (rg_cached_raw_mem_addr == req_raw_mem_addr)
|
|
&& (f_reqs.first.req_op == REQ_OP_WR));
|
|
// Get the old (cached) value of the word64
|
|
Word64_in_Raw_Mem_Word word64_in_raw_mem_word = f_reqs.first.addr [hi_byte_in_raw_mem_word : 3];
|
|
Vector #(Word64s_per_Raw_Mem_Word, Bit #(64)) raw_mem_word_V_Word64 = unpack (rg_cached_raw_mem_word);
|
|
Bit #(64) word64_old = raw_mem_word_V_Word64 [word64_in_raw_mem_word];
|
|
|
|
// Lane-adjust the new word64
|
|
Bit #(64) word64_new = zeroExtend (f_reqs.first.data);
|
|
Bit #(8) strobe = zeroExtend (f_reqs.first.wstrb);
|
|
if ((valueOf (Wd_Data) == 32) && (f_reqs.first.addr [2] == 1'b1)) begin
|
|
// Upper 32b only
|
|
word64_new = { word64_new [31:0], 0 };
|
|
strobe = { strobe [3:0], 0 };
|
|
end
|
|
Bit #(64) mask = fn_strobe_to_mask (strobe);
|
|
let updated_word64 = ((word64_old & (~ mask)) | (word64_new & mask));
|
|
|
|
// Write it back into the cached raw_mem_word
|
|
raw_mem_word_V_Word64 [word64_in_raw_mem_word] = updated_word64;
|
|
rg_cached_raw_mem_word <= pack (raw_mem_word_V_Word64);
|
|
rg_cached_clean <= False;
|
|
|
|
let wrr = AXI4_BFlit {bid: f_reqs.first.id,
|
|
bresp: OKAY,
|
|
buser: 0'b0};
|
|
slavePortShim.master.b.put(wrr);
|
|
f_reqs.deq;
|
|
|
|
if (cfg_verbosity > 1) begin
|
|
$display ("%0d: Mem_Controller.rl_process_wr_req: ", cur_cycle);
|
|
$display (" ", fshow (f_reqs.first));
|
|
$display (" => ", fshow (wrr));
|
|
end
|
|
|
|
// For simulation testing of riscv-tests/isa only:
|
|
if ((rg_watch_tohost)
|
|
&& (f_reqs.first.addr == rg_tohost_addr)
|
|
&& (word64_new != 0))
|
|
begin
|
|
|
|
$display ("%0d: Mem_Controller.rl_process_wr_req: addr 0x%0h (<tohost>) data 0x%0h",
|
|
cur_cycle, f_reqs.first.addr, word64_new);
|
|
let exit_value = (word64_new >> 1);
|
|
if (exit_value == 0)
|
|
$display ("PASS");
|
|
else
|
|
$display ("FAIL %0d", exit_value);
|
|
rg_status <= fromInteger (status_mem_controller_terminated);
|
|
end
|
|
endrule
|
|
|
|
// ================================================================
|
|
// Zero-memory FSM: zero out memory.
|
|
// If needed, we must provide a way to enable it from the debug_module
|
|
// This rule should be enabled with:
|
|
// rg_cached_raw_mem_addr <= 0;
|
|
// rg_state <= STATE_ZEROING_MEM;
|
|
|
|
`ifdef INCLUDE_INITIAL_MEMZERO
|
|
rule rl_zero_mem (rg_state == STATE_ZEROING_MEM);
|
|
let raw_mem_req = MemoryRequest {write: True,
|
|
byteen: '1,
|
|
address: rg_cached_raw_mem_addr,
|
|
data: 0};
|
|
f_raw_mem_reqs.enq (raw_mem_req);
|
|
|
|
// Last write
|
|
let raw_mem_words_per_mem = fn_raw_mem_words_per_mem (rg_addr_base, rg_addr_lim);
|
|
if (rg_cached_raw_mem_addr == (raw_mem_words_per_mem - 1)) begin
|
|
rg_cached_raw_mem_addr <= rg_cached_raw_mem_addr;
|
|
rg_cached_raw_mem_word <= unpack (0);
|
|
rg_cached_clean <= True;
|
|
rg_state <= STATE_READY;
|
|
|
|
// if (cfg_verbosity != 0)
|
|
$display ("%0d: Mem_Controller: zeroed %0d raw-memory locations (%0d-bit words)",
|
|
cur_cycle, raw_mem_words_per_mem, valueOf (Bits_per_Raw_Mem_Word));
|
|
end
|
|
else
|
|
rg_cached_raw_mem_addr <= rg_cached_raw_mem_addr + 1;
|
|
endrule
|
|
`endif
|
|
|
|
// ================================================================
|
|
// Invalid address
|
|
|
|
rule rl_invalid_rd_address ( (rg_state == STATE_READY)
|
|
&& (! fn_addr_is_ok (rg_addr_base, f_reqs.first.addr, rg_addr_lim, f_reqs.first.size))
|
|
&& (f_reqs.first.req_op == REQ_OP_RD));
|
|
Fabric_Data rdata = zeroExtend (f_reqs.first.addr);
|
|
let rdr = AXI4_RFlit {rid: f_reqs.first.id,
|
|
rdata: rdata, // for debugging only
|
|
rresp: SLVERR,
|
|
rlast: True,
|
|
ruser: 0'b0};
|
|
slavePortShim.master.r.put(rdr);
|
|
f_reqs.deq;
|
|
|
|
$write ("%0d: ERROR: Mem_Controller:", cur_cycle);
|
|
if (! fn_addr_is_aligned (f_reqs.first.addr, f_reqs.first.size))
|
|
$display (" read-addr is misaligned");
|
|
else
|
|
$display (" read-addr is out of bounds");
|
|
$display (" rg_addr_base 0x%0h rg_addr_lim 0x%0h", rg_addr_base, rg_addr_lim);
|
|
$display (" ", fshow (f_reqs.first));
|
|
$display (" => ", fshow (rdr));
|
|
endrule
|
|
|
|
rule rl_invalid_wr_address ( (rg_state == STATE_READY)
|
|
&& (! fn_addr_is_ok (rg_addr_base, f_reqs.first.addr, rg_addr_lim, f_reqs.first.size))
|
|
&& (f_reqs.first.req_op == REQ_OP_WR));
|
|
let wrr = AXI4_BFlit {bid: f_reqs.first.id,
|
|
bresp: SLVERR,
|
|
buser: 0'b0};
|
|
slavePortShim.master.b.put(wrr);
|
|
f_reqs.deq;
|
|
|
|
$write ("%0d: ERROR: Mem_Controller:", cur_cycle);
|
|
if (! fn_addr_is_aligned (f_reqs.first.addr, f_reqs.first.size))
|
|
$display (" write-addr is misaligned");
|
|
else
|
|
$display (" write-addr is out of bounds");
|
|
$display (" rg_addr_base 0x%0h rg_addr_lim 0x%0h", rg_addr_base, rg_addr_lim);
|
|
$display (" ", fshow (f_reqs.first));
|
|
$display (" => ", fshow (wrr));
|
|
endrule
|
|
|
|
// ================================================================
|
|
// INTERFACE
|
|
|
|
// Reset
|
|
interface server_reset = toGPServer (f_reset_reqs, f_reset_rsps);
|
|
|
|
// set_addr_map should be called after this module's reset
|
|
method Action set_addr_map (Fabric_Addr addr_base, Fabric_Addr addr_lim) if (rg_state == STATE_READY);
|
|
rg_addr_base <= addr_base;
|
|
rg_addr_lim <= addr_lim;
|
|
$display ("%0d: Mem_Controller.set_addr_map: addr_base 0x%0h addr_lim 0x%0h",
|
|
cur_cycle, addr_base, addr_lim);
|
|
|
|
`ifdef INCLUDE_INITIAL_MEMZERO
|
|
rg_cached_raw_mem_addr <= 0;
|
|
rg_state <= STATE_ZEROING_MEM;
|
|
$display ("%0d: Mem_Controller.set_addr_map: zeroing memory from 0x%0h to 0x%0h",
|
|
cur_cycle, addr_base, addr_lim);
|
|
`endif
|
|
endmethod
|
|
|
|
// Main Fabric Reqs/Rsps
|
|
interface slave = slavePortShim.slave;
|
|
|
|
// To raw memory (outside the SoC)
|
|
interface to_raw_mem = toGPClient (f_raw_mem_reqs, f_raw_mem_rsps);
|
|
|
|
// Catch-all status; return-value can identify the origin (0 = none)
|
|
method Bit #(8) status;
|
|
return rg_status;
|
|
endmethod
|
|
|
|
// For ISA tests: watch memory writes to <tohost> addr
|
|
method Action set_watch_tohost (Bool watch_tohost, Fabric_Addr tohost_addr);
|
|
rg_watch_tohost <= watch_tohost;
|
|
rg_tohost_addr <= tohost_addr;
|
|
endmethod
|
|
endmodule
|
|
|
|
// ================================================================
|
|
|
|
endpackage
|