add fetch::fetch and fetch::l1_i_cache with some testing
Some checks failed
/ test (pull_request) Failing after 25m25s
Some checks failed
/ test (pull_request) Failing after 25m25s
This commit is contained in:
parent
c62d33048c
commit
fff8cf32c1
7 changed files with 25359 additions and 4 deletions
|
|
@ -37,6 +37,9 @@ pub struct CpuConfig {
|
|||
pub max_branches_per_fetch: NonZeroUsize,
|
||||
pub max_fetches_in_flight: NonZeroUsize,
|
||||
pub log2_fetch_width_in_bytes: u8,
|
||||
pub log2_cache_line_size_in_bytes: u8,
|
||||
pub log2_l1_i_cache_line_count: u8,
|
||||
pub l1_i_cache_max_misses_in_flight: NonZeroUsize,
|
||||
/// default value for [`UnitConfig::max_in_flight`]
|
||||
pub default_unit_max_in_flight: NonZeroUsize,
|
||||
pub rob_size: NonZeroUsize,
|
||||
|
|
@ -63,6 +66,14 @@ impl CpuConfig {
|
|||
v
|
||||
};
|
||||
pub const DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES: u8 = 3;
|
||||
pub const DEFAULT_LOG2_CACHE_LINE_SIZE_IN_BYTES: u8 = 6;
|
||||
pub const DEFAULT_LOG2_L1_I_CACHE_LINE_COUNT: u8 = 8;
|
||||
pub const DEFAULT_L1_I_CACHE_MAX_MISSES_IN_FLIGHT: NonZeroUsize = {
|
||||
let Some(v) = NonZeroUsize::new(2) else {
|
||||
unreachable!();
|
||||
};
|
||||
v
|
||||
};
|
||||
pub const DEFAULT_UNIT_MAX_IN_FLIGHT: NonZeroUsize = {
|
||||
let Some(v) = NonZeroUsize::new(8) else {
|
||||
unreachable!();
|
||||
|
|
@ -77,6 +88,9 @@ impl CpuConfig {
|
|||
max_branches_per_fetch: Self::DEFAULT_MAX_BRANCHES_PER_FETCH,
|
||||
max_fetches_in_flight: Self::DEFAULT_MAX_FETCHES_IN_FLIGHT,
|
||||
log2_fetch_width_in_bytes: Self::DEFAULT_LOG2_FETCH_WIDTH_IN_BYTES,
|
||||
log2_cache_line_size_in_bytes: Self::DEFAULT_LOG2_CACHE_LINE_SIZE_IN_BYTES,
|
||||
log2_l1_i_cache_line_count: Self::DEFAULT_LOG2_L1_I_CACHE_LINE_COUNT,
|
||||
l1_i_cache_max_misses_in_flight: Self::DEFAULT_L1_I_CACHE_MAX_MISSES_IN_FLIGHT,
|
||||
default_unit_max_in_flight: Self::DEFAULT_UNIT_MAX_IN_FLIGHT,
|
||||
rob_size,
|
||||
}
|
||||
|
|
@ -141,6 +155,37 @@ impl CpuConfig {
|
|||
.checked_shl(self.log2_fetch_width_in_bytes.into())
|
||||
.expect("log2_fetch_width_in_bytes is too big")
|
||||
}
|
||||
pub fn cache_line_size_in_bytes(&self) -> usize {
|
||||
1usize
|
||||
.checked_shl(self.log2_cache_line_size_in_bytes.into())
|
||||
.expect("log2_cache_line_size_in_bytes is too big")
|
||||
}
|
||||
pub fn log2_fetches_per_cache_line(&self) -> usize {
|
||||
self.log2_cache_line_size_in_bytes
|
||||
.checked_sub(self.log2_fetch_width_in_bytes)
|
||||
.expect("cache line size in bytes must not be smaller than fetch width in bytes")
|
||||
.into()
|
||||
}
|
||||
pub fn fetches_per_cache_line(&self) -> usize {
|
||||
self.log2_fetches_per_cache_line()
|
||||
.try_into()
|
||||
.ok()
|
||||
.and_then(|v| 1usize.checked_shl(v))
|
||||
.expect("log2_fetches_per_cache_line is too big")
|
||||
}
|
||||
pub fn l1_i_cache_line_count(&self) -> usize {
|
||||
1usize
|
||||
.checked_shl(self.log2_l1_i_cache_line_count.into())
|
||||
.expect("log2_l1_i_cache_line_count is too big")
|
||||
}
|
||||
pub fn log2_l1_i_cache_size_in_bytes(&self) -> usize {
|
||||
self.log2_l1_i_cache_line_count as usize + self.log2_cache_line_size_in_bytes as usize
|
||||
}
|
||||
pub fn l1_i_cache_size_in_bytes(&self) -> usize {
|
||||
1usize
|
||||
.checked_shl(self.log2_l1_i_cache_size_in_bytes() as _)
|
||||
.expect("L1 I-Cache is too big")
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl(get(|c| c.fetch_width.get()))]
|
||||
|
|
@ -161,6 +206,33 @@ pub type CpuConfigLog2FetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize
|
|||
#[hdl(get(|c| c.fetch_width_in_bytes()))]
|
||||
pub type CpuConfigFetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.log2_fetches_per_cache_line()))]
|
||||
pub type CpuConfigLog2FetchesPerCacheLine<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.fetches_per_cache_line()))]
|
||||
pub type CpuConfigFetchesPerCacheLine<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.log2_cache_line_size_in_bytes.into()))]
|
||||
pub type CpuConfigLog2CacheLineSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.cache_line_size_in_bytes()))]
|
||||
pub type CpuConfigCacheLineSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.log2_l1_i_cache_line_count.into()))]
|
||||
pub type CpuConfigLog2L1ICacheLineCount<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.l1_i_cache_line_count()))]
|
||||
pub type CpuConfigL1ICacheLineCount<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.log2_l1_i_cache_size_in_bytes()))]
|
||||
pub type CpuConfigLog2L1ICacheSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.l1_i_cache_size_in_bytes()))]
|
||||
pub type CpuConfigL1ICacheSizeInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.l1_i_cache_max_misses_in_flight.get()))]
|
||||
pub type CpuConfigL1ICacheMaxMissesInFlight<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.rob_size.get()))]
|
||||
pub type CpuConfigRobSize<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
|
|
|
|||
1581
crates/cpu/src/fetch.rs
Normal file
1581
crates/cpu/src/fetch.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,7 @@
|
|||
// See Notices.txt for copyright information
|
||||
pub mod config;
|
||||
pub mod decoder;
|
||||
pub mod fetch;
|
||||
pub mod instruction;
|
||||
pub mod next_pc;
|
||||
pub mod powerisa_instructions_xml;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ pub struct NextPcToFetchInterface<C: PhantomConstGet<CpuConfig>> {
|
|||
pub fetch: ReadyValid<NextPcToFetchInterfaceInner>,
|
||||
/// when both fetch and cancel are triggered in the same clock cycle, that means to cancel and then start a new fetch
|
||||
pub cancel: ReadyValid<UIntInRangeInclusiveType<ConstUsize<1>, CpuConfigMaxFetchesInFlight<C>>>,
|
||||
/// for debugging
|
||||
#[hdl(flip)]
|
||||
pub next_fetch_block_ids:
|
||||
HdlOption<ArrayVec<UInt<{ FETCH_BLOCK_ID_WIDTH }>, CpuConfigMaxFetchesInFlight<C>>>,
|
||||
|
|
@ -2719,13 +2720,13 @@ impl SimValueDefault for BranchPredictionState {
|
|||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[must_use]
|
||||
enum ResetStatus {
|
||||
pub(crate) enum ResetStatus {
|
||||
Done,
|
||||
Working,
|
||||
}
|
||||
|
||||
impl ResetStatus {
|
||||
fn and(self, other: Self) -> Self {
|
||||
pub(crate) fn and(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(ResetStatus::Done, ResetStatus::Done) => ResetStatus::Done,
|
||||
(ResetStatus::Done | ResetStatus::Working, ResetStatus::Working)
|
||||
|
|
@ -2734,7 +2735,7 @@ impl ResetStatus {
|
|||
}
|
||||
}
|
||||
|
||||
trait SimValueDefault: Type {
|
||||
pub(crate) trait SimValueDefault: Type {
|
||||
fn sim_value_default(self) -> SimValue<Self>;
|
||||
}
|
||||
|
||||
|
|
@ -2828,7 +2829,7 @@ impl SimValueDefault for WipDecodedInsn {
|
|||
}
|
||||
}
|
||||
|
||||
trait ResetSteps: Type {
|
||||
pub(crate) trait ResetSteps: Type {
|
||||
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,24 @@
|
|||
// See Notices.txt for copyright information
|
||||
|
||||
use fayalite::{expr::ops::ExprIndex, int::UIntInRangeInclusiveType, prelude::*};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArrayVecFullError<V, I: Iterator> {
|
||||
pub value: V,
|
||||
pub rest: std::iter::Chain<std::iter::Once<I::Item>, I>,
|
||||
}
|
||||
|
||||
impl<V, I: Iterator> fmt::Display for ArrayVecFullError<V, I> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "ArrayVec is full")
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: fmt::Debug, I: Iterator<Item: fmt::Debug> + fmt::Debug> std::error::Error
|
||||
for ArrayVecFullError<V, I>
|
||||
{
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
pub type Length<Max: Size> = UIntInRangeInclusiveType<ConstUsize<0>, Max>;
|
||||
|
|
@ -46,6 +64,30 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
|
|||
len: self.elements.len().to_sim_value_with_type(self.len),
|
||||
}
|
||||
}
|
||||
pub fn from_iter_sim<I: IntoIterator<Item: ToSimValueWithType<T>>>(
|
||||
self,
|
||||
uninit_element: impl ToSimValueWithType<T>,
|
||||
iter: I,
|
||||
) -> Result<SimValue<Self>, ArrayVecFullError<SimValue<Self>, I::IntoIter>> {
|
||||
let mut value = Self::new_sim(self, uninit_element);
|
||||
let element = self.element();
|
||||
let mut iter = iter.into_iter();
|
||||
for i in 0..self.capacity() {
|
||||
let Some(v) = iter.next() else {
|
||||
break;
|
||||
};
|
||||
value.elements[i] = v.into_sim_value_with_type(element);
|
||||
*value.len = i + 1;
|
||||
}
|
||||
if let Some(extra) = iter.next() {
|
||||
Err(ArrayVecFullError {
|
||||
value,
|
||||
rest: std::iter::once(extra).chain(iter),
|
||||
})
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
pub fn element(self) -> T {
|
||||
self.elements.element()
|
||||
}
|
||||
|
|
|
|||
22960
crates/cpu/tests/expected/fetch.vcd
generated
Normal file
22960
crates/cpu/tests/expected/fetch.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
698
crates/cpu/tests/fetch.rs
Normal file
698
crates/cpu/tests/fetch.rs
Normal file
|
|
@ -0,0 +1,698 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use cpu::{
|
||||
config::{CpuConfig, UnitConfig},
|
||||
fetch::{
|
||||
FetchToDecodeInterface, FetchToDecodeInterfaceInner, MemoryInterface,
|
||||
MemoryOperationErrorKind, MemoryOperationFinish, MemoryOperationFinishKind,
|
||||
MemoryOperationKind, MemoryOperationStart, fetch,
|
||||
},
|
||||
next_pc::{FETCH_BLOCK_ID_WIDTH, NextPcToFetchInterface, NextPcToFetchInterfaceInner},
|
||||
unit::UnitKind,
|
||||
util::array_vec::ArrayVec,
|
||||
};
|
||||
use fayalite::{
|
||||
prelude::*,
|
||||
sim::vcd::VcdWriterDecls,
|
||||
util::{DebugAsDisplay, RcWriter},
|
||||
};
|
||||
use std::{cell::RefCell, collections::VecDeque, fmt, num::NonZeroUsize};
|
||||
|
||||
struct Random {
|
||||
index: u64,
|
||||
}
|
||||
|
||||
impl Random {
|
||||
fn next(&mut self) -> u64 {
|
||||
let index = self.index;
|
||||
self.index = self.index.wrapping_add(1);
|
||||
// make a pseudo-random number deterministically based on index
|
||||
index
|
||||
.wrapping_add(1)
|
||||
.wrapping_mul(0x18C49126EABE7A0D) // random prime
|
||||
.rotate_left(32)
|
||||
.wrapping_mul(0x92B38C197608A6B) // random prime
|
||||
.rotate_right(60)
|
||||
}
|
||||
}
|
||||
|
||||
const MEMORY_QUEUE_SIZE: usize = 16;
|
||||
|
||||
#[hdl]
|
||||
struct MemoryQueueEntry {
|
||||
addr: UInt<64>,
|
||||
fetch_block_id: UInt<{ FETCH_BLOCK_ID_WIDTH }>,
|
||||
cycles_left: UInt<8>,
|
||||
}
|
||||
|
||||
impl MemoryQueueEntry {
|
||||
#[hdl]
|
||||
fn default_sim(self) -> SimValue<Self> {
|
||||
#[hdl(sim)]
|
||||
Self {
|
||||
addr: 0u64,
|
||||
fetch_block_id: self.fetch_block_id.zero(),
|
||||
cycles_left: 0u8,
|
||||
}
|
||||
}
|
||||
fn get_next_delay(random: &mut Random) -> u8 {
|
||||
if random.next() % 32 == 0 { 30 } else { 5 }
|
||||
}
|
||||
}
|
||||
|
||||
const MEMORY_DATA: &str = "Test data, testing...\nTest Test!\nSecond Cache Line\nTesting.....\n";
|
||||
const MEMORY_START: u64 = 0x1000;
|
||||
const MEMORY_RANGE2: std::ops::Range<u64> = 0x2000..0x3000;
|
||||
const MEMORY_ERROR_RANGE: std::ops::Range<u64> = 0x10F00..0x20F00;
|
||||
const MEMORY_ERROR_STEP: u64 = 0x1000;
|
||||
|
||||
fn read_memory(start: u64, len: usize) -> Option<&'static [u8]> {
|
||||
if MEMORY_ERROR_RANGE.contains(&start) {
|
||||
let start = start - MEMORY_ERROR_RANGE.start;
|
||||
let fail_at = start / MEMORY_ERROR_STEP;
|
||||
let offset = start % MEMORY_ERROR_STEP;
|
||||
return if offset < fail_at {
|
||||
[0xFFu8; MEMORY_DATA.len()].get(..len)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
if MEMORY_RANGE2.contains(&start) {
|
||||
return [0xF2u8; MEMORY_DATA.len()].get(..len);
|
||||
}
|
||||
MEMORY_DATA
|
||||
.as_bytes()
|
||||
.get(start.checked_sub(MEMORY_START)?.try_into().ok()?..)?
|
||||
.get(..len)
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn mock_memory(config: PhantomConst<CpuConfig>) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
|
||||
m.input(MemoryInterface[config]);
|
||||
#[hdl]
|
||||
let queue_debug: ArrayVec<MemoryQueueEntry, ConstUsize<{ MEMORY_QUEUE_SIZE }>> = m.output();
|
||||
m.register_clock_for_past(cd.clk);
|
||||
m.extern_module_simulation_fn(
|
||||
(cd, memory_interface, queue_debug),
|
||||
|(cd, memory_interface, queue_debug), mut sim| async move {
|
||||
// intentionally have a different sequence each time we're reset
|
||||
let random = RefCell::new(Random { index: 0 });
|
||||
sim.resettable(
|
||||
cd,
|
||||
async |mut sim| {
|
||||
#[hdl]
|
||||
let MemoryInterface::<_> {
|
||||
start,
|
||||
finish,
|
||||
next_fetch_block_ids,
|
||||
config: _,
|
||||
} = memory_interface;
|
||||
sim.write(start.ready, false).await;
|
||||
sim.write(finish.data, finish.ty().data.HdlNone()).await;
|
||||
sim.write(next_fetch_block_ids, next_fetch_block_ids.ty().HdlNone())
|
||||
.await;
|
||||
sim.write(
|
||||
queue_debug,
|
||||
queue_debug.ty().new_sim(MemoryQueueEntry.default_sim()),
|
||||
)
|
||||
.await;
|
||||
},
|
||||
|sim, ()| run_fn(cd, memory_interface, queue_debug, &random, sim),
|
||||
)
|
||||
.await;
|
||||
},
|
||||
);
|
||||
#[hdl]
|
||||
async fn run_fn(
|
||||
cd: Expr<ClockDomain>,
|
||||
memory_interface: Expr<MemoryInterface<PhantomConst<CpuConfig>>>,
|
||||
queue_debug: Expr<ArrayVec<MemoryQueueEntry, ConstUsize<{ MEMORY_QUEUE_SIZE }>>>,
|
||||
random: &RefCell<Random>,
|
||||
mut sim: ExternModuleSimulationState,
|
||||
) {
|
||||
let mut random = random.borrow_mut();
|
||||
let config = memory_interface.config.ty();
|
||||
let finish_data_ty = memory_interface.finish.data.ty();
|
||||
let next_fetch_block_ids_ty = memory_interface.next_fetch_block_ids.ty();
|
||||
let mut queue: VecDeque<SimValue<MemoryQueueEntry>> = VecDeque::new();
|
||||
loop {
|
||||
for entry in &mut queue {
|
||||
entry.cycles_left = entry.cycles_left.as_int().saturating_sub(1).to_sim_value();
|
||||
}
|
||||
let sim_queue = queue_debug
|
||||
.ty()
|
||||
.from_iter_sim(MemoryQueueEntry.default_sim(), &queue)
|
||||
.ok()
|
||||
.expect("queue is known to be small enough");
|
||||
sim.write(queue_debug, sim_queue).await;
|
||||
sim.write_bool(
|
||||
memory_interface.start.ready,
|
||||
queue.len() < MEMORY_QUEUE_SIZE && random.next() % 32 != 0,
|
||||
)
|
||||
.await;
|
||||
sim.write(
|
||||
memory_interface.next_fetch_block_ids,
|
||||
#[hdl(sim)]
|
||||
next_fetch_block_ids_ty.HdlSome(
|
||||
next_fetch_block_ids_ty
|
||||
.HdlSome
|
||||
.from_iter_sim(0u8, queue.iter().map(|entry| &entry.fetch_block_id))
|
||||
.ok()
|
||||
.expect("queue is known to be small enough"),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let finish_data = if let Some(entry) = queue
|
||||
.front()
|
||||
.filter(|entry| entry.cycles_left.as_int() == 0)
|
||||
{
|
||||
#[hdl(sim)]
|
||||
let MemoryQueueEntry {
|
||||
addr,
|
||||
fetch_block_id: _,
|
||||
cycles_left: _,
|
||||
} = entry;
|
||||
let addr = addr.as_int();
|
||||
let mut read_data =
|
||||
repeat(0u8, finish_data_ty.HdlSome.read_data.len()).to_sim_value();
|
||||
let kind = if let Some(data) = read_memory(addr, read_data.len()) {
|
||||
for (l, r) in read_data.iter_mut().zip(data) {
|
||||
*l = r.to_sim_value();
|
||||
}
|
||||
#[hdl(sim)]
|
||||
MemoryOperationFinishKind.Success(
|
||||
#[hdl(sim)]
|
||||
MemoryOperationKind.Read(),
|
||||
)
|
||||
} else {
|
||||
#[hdl(sim)]
|
||||
MemoryOperationFinishKind.Error(
|
||||
#[hdl(sim)]
|
||||
MemoryOperationErrorKind.Generic(),
|
||||
)
|
||||
};
|
||||
#[hdl(sim)]
|
||||
finish_data_ty.HdlSome(
|
||||
#[hdl(sim)]
|
||||
MemoryOperationFinish::<_> {
|
||||
kind,
|
||||
read_data,
|
||||
config,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
#[hdl(sim)]
|
||||
finish_data_ty.HdlNone()
|
||||
};
|
||||
sim.write(memory_interface.finish.data, &finish_data).await;
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
println!(
|
||||
"Dump mock memory queue: {:#?}",
|
||||
Vec::from_iter(queue.iter().map(|v| {
|
||||
DebugAsDisplay(format!(
|
||||
"fid={:#x} addr={:#x}",
|
||||
v.fetch_block_id.as_int(),
|
||||
v.addr.as_int(),
|
||||
))
|
||||
}))
|
||||
);
|
||||
if sim
|
||||
.read_past_bool(memory_interface.start.ready, cd.clk)
|
||||
.await
|
||||
{
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(memory_operation_start) =
|
||||
sim.read_past(memory_interface.start.data, cd.clk).await
|
||||
{
|
||||
#[hdl(sim)]
|
||||
let MemoryOperationStart::<_> {
|
||||
kind,
|
||||
addr,
|
||||
write_data: _,
|
||||
fetch_block_id,
|
||||
config: _,
|
||||
} = memory_operation_start;
|
||||
#[hdl(sim)]
|
||||
match kind {
|
||||
MemoryOperationKind::Read => {}
|
||||
MemoryOperationKind::Write => unreachable!(),
|
||||
}
|
||||
let entry = #[hdl(sim)]
|
||||
MemoryQueueEntry {
|
||||
addr,
|
||||
fetch_block_id,
|
||||
cycles_left: MemoryQueueEntry::get_next_delay(&mut random),
|
||||
};
|
||||
println!("mock memory start: {entry:#?}");
|
||||
queue.push_back(entry);
|
||||
}
|
||||
}
|
||||
if sim
|
||||
.read_past_bool(memory_interface.finish.ready, cd.clk)
|
||||
.await
|
||||
{
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(finish_data) = finish_data {
|
||||
let Some(entry) = queue.pop_front() else {
|
||||
unreachable!();
|
||||
};
|
||||
#[hdl(sim)]
|
||||
let MemoryOperationFinish::<_> {
|
||||
kind,
|
||||
read_data,
|
||||
config: _,
|
||||
} = finish_data;
|
||||
let kind = #[hdl(sim)]
|
||||
match kind {
|
||||
MemoryOperationFinishKind::Error(_) => Err(()),
|
||||
MemoryOperationFinishKind::Success(_) => Ok(()),
|
||||
};
|
||||
println!(
|
||||
"mock memory finish: kind={kind:?} read_data={read_data:?} {entry:#?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
fn dut(config: PhantomConst<CpuConfig>) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
|
||||
m.input(NextPcToFetchInterface[config]);
|
||||
#[hdl]
|
||||
let to_decode: FetchToDecodeInterface<PhantomConst<CpuConfig>> =
|
||||
m.output(FetchToDecodeInterface[config]);
|
||||
#[hdl]
|
||||
let fetch = instance(fetch(config));
|
||||
#[hdl]
|
||||
let fetch {
|
||||
cd: fetch_cd,
|
||||
memory_interface: fetch_memory_interface,
|
||||
from_next_pc: fetch_from_next_pc,
|
||||
to_decode: fetch_to_decode,
|
||||
} = fetch;
|
||||
connect(fetch_cd, cd);
|
||||
connect(fetch_from_next_pc, from_next_pc);
|
||||
connect(to_decode, fetch_to_decode);
|
||||
#[hdl]
|
||||
let mock_memory = instance(mock_memory(config));
|
||||
#[hdl]
|
||||
let mock_memory {
|
||||
cd: mock_memory_cd,
|
||||
memory_interface: mock_memory_interface,
|
||||
queue_debug: _,
|
||||
} = mock_memory;
|
||||
connect(mock_memory_cd, cd);
|
||||
connect(mock_memory_interface, fetch_memory_interface);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FetchTestOperation {
|
||||
start_pc: u64,
|
||||
fetch_block_id: u8,
|
||||
fetch_block_data: [u8; FETCH_WIDTH_IN_BYTES],
|
||||
error: Option<SimValue<MemoryOperationErrorKind>>,
|
||||
}
|
||||
|
||||
impl PartialEq for FetchTestOperation {
|
||||
#[hdl]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let Self {
|
||||
start_pc,
|
||||
fetch_block_id,
|
||||
fetch_block_data,
|
||||
ref error,
|
||||
} = *self;
|
||||
if let Some(error) = error {
|
||||
#[hdl(sim)]
|
||||
match error {
|
||||
MemoryOperationErrorKind::Generic => {}
|
||||
}
|
||||
}
|
||||
start_pc == other.start_pc
|
||||
&& fetch_block_id == other.fetch_block_id
|
||||
&& fetch_block_data == other.fetch_block_data
|
||||
&& error.is_some() == other.error.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FetchTestOperation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self {
|
||||
start_pc,
|
||||
fetch_block_id,
|
||||
fetch_block_data,
|
||||
error,
|
||||
} = self;
|
||||
let mut debug_struct = f.debug_struct("FetchTestOperation");
|
||||
debug_struct.field("start_pc", &format_args!("{start_pc:#x}"));
|
||||
debug_struct.field("fetch_block_id", &format_args!("{fetch_block_id:#x}"));
|
||||
if fetch_block_data.iter().all(|v| *v == fetch_block_data[0]) {
|
||||
debug_struct.field(
|
||||
"fetch_block_data",
|
||||
&format_args!(
|
||||
"[b'{}'; {FETCH_WIDTH_IN_BYTES}]",
|
||||
fetch_block_data[0].escape_ascii(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
debug_struct.field(
|
||||
"fetch_block_data",
|
||||
&format_args!("b\"{}\"", fetch_block_data.escape_ascii()),
|
||||
);
|
||||
}
|
||||
debug_struct.field("error", error).finish()
|
||||
}
|
||||
}
|
||||
|
||||
const LOG2_FETCH_WIDTH_IN_BYTES: u8 = 3;
|
||||
const FETCH_WIDTH_IN_BYTES: usize = 1 << LOG2_FETCH_WIDTH_IN_BYTES;
|
||||
const LOG2_CACHE_LINE_SIZE_IN_BYTES: u8 = 5;
|
||||
const CACHE_LINE_SIZE_IN_BYTES: usize = 1 << LOG2_CACHE_LINE_SIZE_IN_BYTES;
|
||||
|
||||
// needs to be a multiple of the cache line size
|
||||
const _: [(); CACHE_LINE_SIZE_IN_BYTES * 2] = [(); MEMORY_DATA.len()];
|
||||
|
||||
fn fetch_test_operations() -> Vec<FetchTestOperation> {
|
||||
#[track_caller]
|
||||
fn mem_data(r: std::ops::RangeFrom<usize>) -> [u8; FETCH_WIDTH_IN_BYTES] {
|
||||
*MEMORY_DATA[r]
|
||||
.as_bytes()
|
||||
.first_chunk()
|
||||
.expect("start should be in-range")
|
||||
}
|
||||
#[hdl]
|
||||
fn generic_error() -> SimValue<MemoryOperationErrorKind> {
|
||||
#[hdl(sim)]
|
||||
MemoryOperationErrorKind.Generic()
|
||||
}
|
||||
let mut last_fetch_block_id = 0u8.wrapping_sub(1);
|
||||
macro_rules! op {
|
||||
{
|
||||
$($field:ident: $value:expr,)*
|
||||
} => {
|
||||
FetchTestOperation {
|
||||
fetch_block_id: {
|
||||
last_fetch_block_id = last_fetch_block_id.wrapping_add(1);
|
||||
last_fetch_block_id
|
||||
},
|
||||
$($field: $value,)*
|
||||
}
|
||||
};
|
||||
}
|
||||
vec![
|
||||
op! {
|
||||
start_pc: 0x1000,
|
||||
fetch_block_data: mem_data(0..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1010,
|
||||
fetch_block_data: mem_data(0x10..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1020,
|
||||
fetch_block_data: mem_data(0x20..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x100,
|
||||
fetch_block_data: [0; _],
|
||||
error: Some(generic_error()),
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_ERROR_RANGE.start,
|
||||
fetch_block_data: [0; _],
|
||||
error: Some(generic_error()),
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_ERROR_RANGE.start + MEMORY_ERROR_STEP,
|
||||
fetch_block_data: [0; _],
|
||||
error: Some(generic_error()),
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_ERROR_RANGE.start + MEMORY_ERROR_STEP * 2,
|
||||
fetch_block_data: [0; _],
|
||||
error: Some(generic_error()),
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_ERROR_RANGE.start + MEMORY_ERROR_STEP * 3,
|
||||
fetch_block_data: [0; _],
|
||||
error: Some(generic_error()),
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1000,
|
||||
fetch_block_data: mem_data(0..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1010,
|
||||
fetch_block_data: mem_data(0x10..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1020,
|
||||
fetch_block_data: mem_data(0x20..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1000,
|
||||
fetch_block_data: mem_data(0..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1010,
|
||||
fetch_block_data: mem_data(0x10..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1020,
|
||||
fetch_block_data: mem_data(0x20..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_RANGE2.start,
|
||||
fetch_block_data: [0xF2; _],
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_RANGE2.start,
|
||||
fetch_block_data: [0xF2; _],
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_RANGE2.start,
|
||||
fetch_block_data: [0xF2; _],
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_RANGE2.start,
|
||||
fetch_block_data: [0xF2; _],
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_RANGE2.start,
|
||||
fetch_block_data: [0xF2; _],
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_RANGE2.start,
|
||||
fetch_block_data: [0xF2; _],
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: MEMORY_RANGE2.start,
|
||||
fetch_block_data: [0xF2; _],
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1000,
|
||||
fetch_block_data: mem_data(0..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1000,
|
||||
fetch_block_data: mem_data(0..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1000,
|
||||
fetch_block_data: mem_data(0..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1000,
|
||||
fetch_block_data: mem_data(0..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1000,
|
||||
fetch_block_data: mem_data(0..),
|
||||
error: None,
|
||||
},
|
||||
op! {
|
||||
start_pc: 0x1000,
|
||||
fetch_block_data: mem_data(0..),
|
||||
error: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[hdl]
|
||||
fn test_fetch() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
let mut config = CpuConfig::new(
|
||||
vec![
|
||||
UnitConfig::new(UnitKind::AluBranch),
|
||||
UnitConfig::new(UnitKind::AluBranch),
|
||||
],
|
||||
NonZeroUsize::new(20).unwrap(),
|
||||
);
|
||||
config.fetch_width = NonZeroUsize::new(2).unwrap();
|
||||
config.log2_fetch_width_in_bytes = LOG2_FETCH_WIDTH_IN_BYTES;
|
||||
config.log2_cache_line_size_in_bytes = LOG2_CACHE_LINE_SIZE_IN_BYTES;
|
||||
config.log2_l1_i_cache_line_count = 4;
|
||||
let m = dut(PhantomConst::new_sized(config));
|
||||
let mut sim = Simulation::new(m);
|
||||
let writer = RcWriter::default();
|
||||
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
|
||||
struct DumpVcdOnDrop {
|
||||
writer: Option<RcWriter>,
|
||||
}
|
||||
impl Drop for DumpVcdOnDrop {
|
||||
fn drop(&mut self) {
|
||||
if let Some(mut writer) = self.writer.take() {
|
||||
let vcd = String::from_utf8(writer.take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut writer = DumpVcdOnDrop {
|
||||
writer: Some(writer),
|
||||
};
|
||||
let from_next_pc_ty = sim.io().from_next_pc.ty();
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, true);
|
||||
sim.write(
|
||||
sim.io().from_next_pc.cancel.data,
|
||||
from_next_pc_ty.cancel.data.HdlNone(),
|
||||
);
|
||||
sim.write(
|
||||
sim.io().from_next_pc.fetch.data,
|
||||
from_next_pc_ty.fetch.data.HdlNone(),
|
||||
);
|
||||
sim.write(sim.io().to_decode.fetched.ready, true);
|
||||
sim.write(
|
||||
sim.io().to_decode.next_fetch_block_ids,
|
||||
HdlSome(
|
||||
sim.io()
|
||||
.ty()
|
||||
.to_decode
|
||||
.next_fetch_block_ids
|
||||
.HdlSome
|
||||
.new_sim(0u8),
|
||||
),
|
||||
);
|
||||
let operations = fetch_test_operations();
|
||||
let mut started_operations = 0;
|
||||
let mut finished_operations = 0;
|
||||
for cycle in 0..150 {
|
||||
sim.write(
|
||||
sim.io().from_next_pc.fetch.data,
|
||||
if let Some(op) = operations.get(started_operations) {
|
||||
#[hdl(sim)]
|
||||
HdlSome(
|
||||
#[hdl(sim)]
|
||||
NextPcToFetchInterfaceInner {
|
||||
start_pc: op.start_pc,
|
||||
fetch_block_id: op.fetch_block_id,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
#[hdl(sim)]
|
||||
HdlNone()
|
||||
},
|
||||
);
|
||||
sim.advance_time(SimDuration::from_nanos(500));
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(next_fetch_block_ids) = sim.read(sim.io().from_next_pc.next_fetch_block_ids)
|
||||
{
|
||||
let next_fetch_block_ids = ArrayVec::elements_sim_ref(&next_fetch_block_ids);
|
||||
let expected_next_fetch_block_ids = Vec::from_iter(
|
||||
operations
|
||||
.get(finished_operations..started_operations)
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(|op| op.fetch_block_id.to_sim_value()),
|
||||
);
|
||||
println!("expected_next_fetch_block_ids={expected_next_fetch_block_ids:?}");
|
||||
assert_eq!(next_fetch_block_ids, expected_next_fetch_block_ids);
|
||||
}
|
||||
if sim.read_bool(sim.io().from_next_pc.fetch.ready) {
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(_) = sim.read(sim.io().from_next_pc.fetch.data) {
|
||||
println!("started fetch: {:#?}", operations[started_operations]);
|
||||
started_operations += 1;
|
||||
}
|
||||
} else {
|
||||
println!("not ready to start fetch");
|
||||
}
|
||||
if sim.read_bool(sim.io().to_decode.fetched.ready) {
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(fetched) = sim.read(sim.io().to_decode.fetched.data) {
|
||||
#[hdl(sim)]
|
||||
let FetchToDecodeInterfaceInner::<_> {
|
||||
start_pc,
|
||||
fetch_block_id,
|
||||
fetch_block_data,
|
||||
error,
|
||||
config: _,
|
||||
} = &fetched;
|
||||
let Some(expected_op) = operations.get(finished_operations) else {
|
||||
panic!("too many finished operations: {fetched:#?}");
|
||||
};
|
||||
let op = FetchTestOperation {
|
||||
start_pc: start_pc.as_int(),
|
||||
fetch_block_id: fetch_block_id.as_int(),
|
||||
error: #[hdl(sim)]
|
||||
match error {
|
||||
HdlSome(e) => Some(e.clone()),
|
||||
HdlNone => None,
|
||||
},
|
||||
fetch_block_data: std::array::from_fn(|i| fetch_block_data[i].as_int()),
|
||||
};
|
||||
println!("finished fetch: op={op:#?}");
|
||||
assert_eq!(
|
||||
op, *expected_op,
|
||||
"cycle={cycle} finished_operations={finished_operations}",
|
||||
);
|
||||
finished_operations += 1;
|
||||
} else {
|
||||
println!("not ready to finish fetch");
|
||||
}
|
||||
}
|
||||
println!("clock tick: {cycle}");
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(SimDuration::from_nanos(500));
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, false);
|
||||
}
|
||||
assert_eq!(finished_operations, operations.len());
|
||||
// FIXME: vcd is just whatever fetch does now, which isn't known to be correct
|
||||
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
if vcd != include_str!("expected/fetch.vcd") {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue