Skip to content

Commit

Permalink
[difftest] Added some documents to async memory
Browse files Browse the repository at this point in the history
  • Loading branch information
CircuitCoder committed Feb 26, 2025
1 parent c1aa256 commit 9872227
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 8 deletions.
3 changes: 2 additions & 1 deletion difftest/dpi_t1rocketemu/src/dpi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub type SvBitVecVal = u32;

static TARGET: DpiTarget<Driver> = DpiTarget::new();

/// Wstrb iterator
struct StrbIterator {
strb: *const u8,
total_width: usize,
Expand All @@ -33,7 +34,7 @@ impl Iterator for StrbIterator {

let slot = self.current / 8;
let bit = self.current % 8;
// TODO: is small endian correct??
// The wstrb are transfered in small endian
let extracted = unsafe { (self.strb.offset(slot as isize).read() >> bit & 1) != 0 };
self.current += 1;
Some(extracted)
Expand Down
32 changes: 28 additions & 4 deletions difftest/dpi_t1rocketemu/src/drive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub struct OnlineArgs {
pub dramsim3_cfg: Option<PathBuf>,
}

/// An incomplete memory write
/// Keeps track of both the data filling and the request into
/// AddressSpace itself
#[derive(Debug)]
pub struct IncompleteWrite {
id: u64,
Expand All @@ -54,8 +57,10 @@ pub struct IncompleteWrite {
width: usize, // In bytes
user: u64,

sent: bool, // Sent to memory system
done: bool, // Address Space has finished this write
/// Is this request already sent to the memory?
sent: bool,
/// Is this request processed by the memory?
done: bool,

data: Vec<u8>,
strb: Vec<bool>,
Expand Down Expand Up @@ -104,6 +109,7 @@ impl IncompleteWrite {
}
}

/// Add an AXI W channel beat
pub fn push(
&mut self,
wdata: &[u8],
Expand Down Expand Up @@ -148,15 +154,21 @@ impl IncompleteWrite {
}
}

/// An incomplete memory read
/// Keeps track of both the data draining and the request into
/// AddressSpace itself
#[derive(Debug)]
pub struct IncompleteRead {
addr: u64,
bursts: usize,
width: usize,
user: u64,

sent: bool, // Sent to memory
/// Is this request sent to memory?
sent: bool,
/// The number of bytes already returned to the RTL
returned: usize,
/// The fetched data. None if the response has not arrived yet
data: Option<Vec<u8>>,

// Used for transfers
Expand Down Expand Up @@ -192,7 +204,9 @@ impl IncompleteRead {
}
}

/// Returns rlast
/// Drain one beat into the AXI R channel
///
/// Returns true if this is the last beat in the response (rlast)
pub fn pop(&mut self, rdata_buf: &mut [u8], data_width: u64) -> bool {
assert!(
self.data.is_some(),
Expand Down Expand Up @@ -383,7 +397,11 @@ impl Driver {
self.last_commit_cycle = get_t();
}

/// Ticking the peripherals
pub fn tick(&mut self) {
// This tick happens on the posedge of each clock
// Also it may be called multiple time because of multiple slaves
// so here we check if we have already ticked this cycle.
let desired_tick = get_t();
if self.next_tick != 0 && desired_tick > self.next_tick {
panic!("Skipped a tick: {} -> {}", self.next_tick, desired_tick);
Expand All @@ -395,6 +413,12 @@ impl Driver {
}
self.next_tick = desired_tick + 1;

// Then this function handles the real ticking, which contains three steps:
// 1. Send all requests
// 2. Ticking the AddressSpace
// 3. Poll the responses
// This way, memory accesses can be returned in the next cycle for peripherals with no latency

// Allow sending multiple
for (cid, fifo) in self.incomplete_writes.iter_mut() {
// Always handled in-order, find first pending
Expand Down
58 changes: 55 additions & 3 deletions difftest/dpi_t1rocketemu/src/interconnect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ impl AddrInfo {
}
}

/// Payload of a memory request
#[derive(Debug)]
pub enum MemReqPayload<'a> {
Read,
Write(&'a [u8], Option<&'a [bool]>),
}

/// A memory request
///
/// The device should keep the ordering of requests of the same ID
#[derive(Debug)]
pub struct MemReq<'a> {
pub id: u64,
Expand All @@ -52,13 +56,22 @@ impl<'a> MemReqPayload<'a> {
}
}

/// Payload of memory response
///
/// Here we distinguish a fixed-sized MMIO register response
/// from a arbitrarily-sized bulk memory response to simplify
/// the implementation of RegDevice, since fixed-sized arrays
/// are not borrowed
#[derive(Debug)]
pub enum MemRespPayload<'a> {
ReadBuffered(&'a [u8]),
ReadRegister([u8; 4]),
WriteAck,
}

/// A memory response
///
/// IDs correlates the response with the request
#[derive(Debug)]
pub struct MemResp<'a> {
pub id: u64,
Expand Down Expand Up @@ -100,6 +113,10 @@ pub trait RegDevice {
}

/// Wrapping a reg device to implement the device trait
///
/// We need a wrapper because we have to keep track of pending
/// MemResps. This is in turn due to the separation of request pushing
/// and response polling.
struct WrappedRegDevice<RD: RegDevice> {
device: RD,
pending: Option<MemResp<'static>>,
Expand Down Expand Up @@ -149,6 +166,7 @@ impl<T: RegDevice + Send + Sync + 'static> Device for WrappedRegDevice<T> {
fn tick(&mut self) {} // This is a no-op for Reg devices
}

/// An abstract memory request (identifier) for memory models
#[derive(Debug)]
pub struct MemIdent {
id: u64,
Expand All @@ -166,12 +184,22 @@ impl Into<MemIdent> for InflightMem {
}
}

/// The memory model interface
///
/// A memory model simulates the latency of a memory device,
/// but don't keep track of its content.
pub trait MemoryModel {
fn push(&mut self, req: MemIdent);
fn pop(&mut self) -> Option<MemIdent>;
fn tick(&mut self);
}

/// AddrSet repersents a subset of a specific memory slice, where
/// each element (line) have uniform width
///
/// A memory request with arbitrary length may need to be segmented
/// into multiple smaller memory requests. AddrSets are used to individually
/// keep track of these requests' progress.
#[derive(Clone, Debug)]
pub struct AddrSet {
pub base: u32,
Expand All @@ -180,6 +208,9 @@ pub struct AddrSet {
}

impl AddrSet {
/// Create an addr set based on the unaligned base, unaligned length, and the line size
///
/// the returned set contains all elements initially.
pub fn new(unaligned_base: u32, len: u32, line_size: u32) -> AddrSet {
let base = (unaligned_base / line_size) * line_size;
let prepend = unaligned_base - base;
Expand All @@ -198,10 +229,14 @@ impl AddrSet {
AddrSet { base, line_size: line_size as u16, set }
}

/// Returns if this set is empty
pub fn empty(&self) -> bool {
self.set == 0
}

/// Remove a element based on its index
///
/// Returns if the element is previously in the set
pub fn remove(&mut self, idx: u32) -> bool {
if idx >= 16 {
return false;
Expand All @@ -211,6 +246,11 @@ impl AddrSet {
currently_set
}

/// Remove a element based on its address
/// The address has to reside within the range, and is aligned
/// to the line size.
///
/// Returns if the element is previously in the set
pub fn remove_addr(&mut self, addr: u32) -> bool {
if addr < self.base {
return false;
Expand Down Expand Up @@ -238,6 +278,7 @@ impl Iterator for AddrSet {
}
}

/// Book-keeping structure for DRAMsim models
#[derive(Debug)]
struct InflightMem {
id: u64,
Expand All @@ -263,19 +304,27 @@ impl InflightMem {
}
}

/// Returns if all sub-requests are sent to the memory model
fn sent(&self) -> bool {
self.req_wait.empty()
}

/// Returns if all sub-responses are received
fn done(&self) -> bool {
self.resp_wait.empty()
}
}

/// The DRAM memory model based on DRAMsim3
///
/// The ticking speed of this model should be the system clock speed,
/// as it keeps track of the DRAM clock tick internally and compensates
/// for the clock speed difference.
pub struct DRAMModel {
sys: dramsim3::MemorySystem,
inflights: Arc<Mutex<Vec<InflightMem>>>,
cached: BinaryHeap<u32>,
// TODO: implement cache
_cached: BinaryHeap<u32>,
dram_tick: usize,
sys_tick: usize,
}
Expand Down Expand Up @@ -307,7 +356,7 @@ impl DRAMModel {
let ret = DRAMModel {
sys,
inflights: inflights,
cached: BinaryHeap::new(),
_cached: BinaryHeap::new(),
dram_tick: 0,
sys_tick: 0,
};
Expand All @@ -316,7 +365,7 @@ impl DRAMModel {
ret
}

// Size of each request to DRAM, in bytes
/// Size of each request to DRAM, in bytes
fn req_size(&self) -> u32 {
(self.sys.burst_length() * self.sys.bus_bits() / 8) as u32
}
Expand Down Expand Up @@ -389,6 +438,8 @@ impl MemoryModel for Arc<Mutex<DRAMModel>> {
}
}

/// A trivial memory model, where all requests are immediately resolved
/// and served in the FIFO order
#[derive(Default)]
pub struct TrivialModel {
holding: VecDeque<MemIdent>,
Expand All @@ -404,6 +455,7 @@ impl MemoryModel for TrivialModel {
fn tick(&mut self) {}
}

/// Repersents a bulk memory device, with its memory model
pub struct RegularMemory<M: MemoryModel> {
data: Vec<u8>,
model: M,
Expand Down

0 comments on commit 9872227

Please sign in to comment.