Compare commits

...
Sign in to create a new pull request.

2 commits

7 changed files with 494 additions and 8 deletions

8
Cargo.lock generated
View file

@ -388,7 +388,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "fayalite"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#1bc835803bf87643c6464eaca0932e30e5febb8c"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#c632e5d570d4763e8e18d764e95b7a9e515ebf99"
dependencies = [
"base64",
"bitvec",
@ -416,7 +416,7 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#1bc835803bf87643c6464eaca0932e30e5febb8c"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#c632e5d570d4763e8e18d764e95b7a9e515ebf99"
dependencies = [
"fayalite-proc-macros-impl",
]
@ -424,7 +424,7 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros-impl"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#1bc835803bf87643c6464eaca0932e30e5febb8c"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#c632e5d570d4763e8e18d764e95b7a9e515ebf99"
dependencies = [
"base16ct 0.2.0",
"num-bigint",
@ -439,7 +439,7 @@ dependencies = [
[[package]]
name = "fayalite-visit-gen"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#1bc835803bf87643c6464eaca0932e30e5febb8c"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#c632e5d570d4763e8e18d764e95b7a9e515ebf99"
dependencies = [
"indexmap",
"prettyplease",

View file

@ -37,6 +37,8 @@ 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 l1_i_cache_line_count: NonZeroUsize,
/// default value for [`UnitConfig::max_in_flight`]
pub default_unit_max_in_flight: NonZeroUsize,
pub rob_size: NonZeroUsize,
@ -63,6 +65,13 @@ 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_L1_I_CACHE_LINE_COUNT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(256) else {
unreachable!();
};
v
};
pub const DEFAULT_UNIT_MAX_IN_FLIGHT: NonZeroUsize = {
let Some(v) = NonZeroUsize::new(8) else {
unreachable!();
@ -77,6 +86,8 @@ 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,
l1_i_cache_line_count: Self::DEFAULT_L1_I_CACHE_LINE_COUNT,
default_unit_max_in_flight: Self::DEFAULT_UNIT_MAX_IN_FLIGHT,
rob_size,
}
@ -141,6 +152,17 @@ 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 l1_i_cache_size_in_bytes(&self) -> usize {
self.l1_i_cache_line_count
.get()
.checked_mul(self.cache_line_size_in_bytes())
.expect("L1 I-Cache is too big")
}
}
#[hdl(get(|c| c.fetch_width.get()))]
@ -161,6 +183,18 @@ 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_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.l1_i_cache_line_count.get()))]
pub type CpuConfigL1ICacheLineCount<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.rob_size.get()))]
pub type CpuConfigRobSize<C: PhantomConstGet<CpuConfig>> = DynSize;

266
crates/cpu/src/fetch.rs Normal file
View file

@ -0,0 +1,266 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
config::{
CpuConfig, CpuConfigCacheLineSizeInBytes, CpuConfigFetchWidthInBytes,
CpuConfigL1ICacheLineCount, PhantomConstCpuConfig,
},
next_pc::{NextPcToFetchInterface, ResetStatus, ResetSteps, SimValueDefault},
};
use fayalite::{
memory::{ReadWriteStruct, memory_addr_width, splat_mask},
prelude::*,
util::ready_valid::ReadyValid,
};
#[hdl]
pub enum MemoryOperationKind {
Read,
Write,
}
#[hdl(no_static)]
pub struct MemoryOperationStart<C: PhantomConstGet<CpuConfig> + PhantomConstCpuConfig> {
pub kind: MemoryOperationKind,
pub addr: UInt<64>,
pub write_data: ArrayType<UInt<8>, CpuConfigFetchWidthInBytes<C>>,
pub config: C,
}
#[hdl]
pub enum MemoryOperationErrorKind {
Generic,
}
#[hdl]
pub enum MemoryOperationFinishKind {
Success(MemoryOperationKind),
Error(MemoryOperationErrorKind),
}
#[hdl(no_static)]
pub struct MemoryOperationFinish<C: PhantomConstGet<CpuConfig> + PhantomConstCpuConfig> {
pub kind: MemoryOperationFinishKind,
pub read_data: ArrayType<UInt<8>, CpuConfigFetchWidthInBytes<C>>,
pub config: C,
}
#[hdl(no_static)]
pub struct MemoryInterface<C: PhantomConstGet<CpuConfig> + PhantomConstCpuConfig> {
pub start: ReadyValid<MemoryOperationStart<C>>,
#[hdl(flip)]
pub finish: ReadyValid<MemoryOperationFinish<C>>,
pub config: C,
}
#[hdl(no_static)]
struct CacheLine<C: PhantomConstGet<CpuConfig> + PhantomConstCpuConfig> {
data: ArrayType<UInt<8>, CpuConfigCacheLineSizeInBytes<C>>,
addr: HdlOption<UInt<64>>,
config: C,
}
#[hdl(no_static)]
struct L1ICacheState<C: PhantomConstGet<CpuConfig> + PhantomConstCpuConfig> {
config: C,
}
impl<C: PhantomConstCpuConfig> SimValueDefault for L1ICacheState<C> {
#[hdl]
fn sim_value_default(self) -> SimValue<Self> {
let Self { config } = self;
#[hdl(sim)]
Self { config }
}
}
impl<C: PhantomConstCpuConfig> ResetSteps for L1ICacheState<C> {
#[hdl]
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus {
#[hdl(sim)]
let Self { config: _ } = this;
ResetStatus::Done
}
}
#[hdl_module(extern)]
fn l1_i_cache_impl(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
m.output(MemoryInterface[config]);
#[hdl]
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
m.input(NextPcToFetchInterface[config]);
// i_cache_port.clk is externally overridden with cd.clk
#[hdl]
let i_cache_port: ReadWriteStruct<CacheLine<PhantomConst<CpuConfig>>, DynSize> = m.output(
ReadWriteStruct[CacheLine[config]][memory_addr_width(CpuConfigL1ICacheLineCount[config])],
);
#[hdl]
let state_for_debug: L1ICacheState<PhantomConst<CpuConfig>> = m.output(L1ICacheState[config]);
m.register_clock_for_past(cd.clk);
#[hdl]
async fn run(
mut sim: ExternModuleSimulationState,
cd: Expr<ClockDomain>,
memory_interface: Expr<MemoryInterface<PhantomConst<CpuConfig>>>,
from_next_pc: Expr<NextPcToFetchInterface<PhantomConst<CpuConfig>>>,
i_cache_port: Expr<ReadWriteStruct<CacheLine<PhantomConst<CpuConfig>>, DynSize>>,
state_expr: Expr<L1ICacheState<PhantomConst<CpuConfig>>>,
) {
let mut state = sim.read(state_expr).await;
let config = state.config.ty();
let l1_i_cache_line_count = CpuConfigL1ICacheLineCount[config];
let cache_line_ty = CacheLine[config];
for step in 0usize.. {
sim.write(state_expr, state).await;
sim.wait_for_clock_edge(cd.clk).await;
state = sim.read_past(state_expr, cd.clk).await;
sim.write(i_cache_port.en, false).await;
let mut reset_status = ResetSteps::reset_step(&mut state, step);
if step < l1_i_cache_line_count {
reset_status = ResetStatus::Working;
#[hdl]
let ReadWriteStruct::<_, _> {
addr,
en,
clk: _, // externally overridden with cd.clk
rdata: _,
wmode,
wdata,
wmask,
} = i_cache_port;
sim.write(addr, step.cast_to(addr.ty())).await;
sim.write(en, true).await;
sim.write(wmode, true).await;
sim.write(
wdata,
#[hdl(sim)]
CacheLine::<_> {
data: repeat(0u8, cache_line_ty.data.len()),
addr: #[hdl(sim)]
HdlNone(),
config,
},
)
.await;
sim.write(wmask, splat_mask(cache_line_ty, true.to_expr()))
.await;
}
match reset_status {
ResetStatus::Done => break,
ResetStatus::Working => {}
}
}
todo!();
}
m.extern_module_simulation_fn(
(
cd,
memory_interface,
from_next_pc,
i_cache_port,
state_for_debug,
),
|(cd, memory_interface, from_next_pc, i_cache_port, state_for_debug), mut sim| async move {
let config = memory_interface.ty().config;
let cache_line_size_in_bytes = CpuConfigCacheLineSizeInBytes[config];
let cache_line_ty = CacheLine[config];
sim.write(i_cache_port.clk, false).await; // externally overridden with cd.clk, so just write a constant here
sim.resettable(
cd,
|mut sim: ExternModuleSimulationState| async move {
sim.write(memory_interface.start.ready, false).await;
sim.write(memory_interface.finish.ready, false).await;
sim.write(
from_next_pc.next_fetch_block_ids,
from_next_pc.ty().next_fetch_block_ids.HdlNone(),
)
.await;
sim.write(from_next_pc.fetch.ready, false).await;
sim.write(from_next_pc.cancel.ready, false).await;
sim.write(i_cache_port.addr, 0u8.cast_to(i_cache_port.addr.ty()))
.await;
sim.write(i_cache_port.en, false).await;
sim.write(i_cache_port.wmode, false).await;
sim.write(
i_cache_port.wdata,
#[hdl(sim)]
CacheLine::<_> {
data: repeat(0u8, cache_line_size_in_bytes),
addr: HdlNone(),
config,
},
)
.await;
sim.write(
i_cache_port.wmask,
splat_mask(cache_line_ty, false.to_expr()),
)
.await;
sim.write(
state_for_debug,
#[hdl(sim)]
L1ICacheState::<_> { config },
)
.await;
},
|sim, ()| {
run(
sim,
cd,
memory_interface,
from_next_pc,
i_cache_port,
state_for_debug,
)
},
)
.await;
},
);
}
#[hdl_module]
pub fn l1_i_cache(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
m.output(MemoryInterface[config]);
#[hdl]
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
m.input(NextPcToFetchInterface[config]);
let cache_line_ty = CacheLine[config];
let cache_line_count = CpuConfigL1ICacheLineCount[config];
#[hdl]
let mut i_cache = memory_array(ArrayType[cache_line_ty][cache_line_count]);
let i_cache_port = i_cache.new_rw_port();
#[hdl]
let l1_i_cache_impl = instance(l1_i_cache_impl(config));
connect(l1_i_cache_impl.cd, cd);
connect(memory_interface, l1_i_cache_impl.memory_interface);
connect(l1_i_cache_impl.from_next_pc, from_next_pc);
connect(i_cache_port, l1_i_cache_impl.i_cache_port);
connect(i_cache_port.clk, cd.clk);
}
#[hdl_module]
pub fn fetch(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
m.output(MemoryInterface[config]);
#[hdl]
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
m.input(NextPcToFetchInterface[config]);
#[hdl]
let l1_i_cache = instance(l1_i_cache(config));
connect(l1_i_cache.cd, cd);
connect(memory_interface, l1_i_cache.memory_interface);
connect(l1_i_cache.from_next_pc, from_next_pc);
}

View file

@ -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;

View file

@ -2719,13 +2719,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 +2734,7 @@ impl ResetStatus {
}
}
trait SimValueDefault: Type {
pub(crate) trait SimValueDefault: Type {
fn sim_value_default(self) -> SimValue<Self>;
}
@ -2828,7 +2828,7 @@ impl SimValueDefault for WipDecodedInsn {
}
}
trait ResetSteps: Type {
pub(crate) trait ResetSteps: Type {
fn reset_step(this: &mut SimValue<Self>, step: usize) -> ResetStatus;
}

0
crates/cpu/tests/expected/fetch.vcd generated Normal file
View file

185
crates/cpu/tests/fetch.rs Normal file
View file

@ -0,0 +1,185 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
config::{CpuConfig, UnitConfig},
fetch::{MemoryInterface, fetch},
next_pc::NextPcToFetchInterface,
unit::UnitKind,
util::array_vec::ArrayVec,
};
use fayalite::{
prelude::*,
sim::vcd::VcdWriterDecls,
util::{DebugAsDisplay, RcWriter},
};
use std::{cell::Cell, collections::VecDeque, num::NonZeroUsize};
const MEMORY_QUEUE_SIZE: usize = 32;
#[hdl]
struct MemoryQueueEntry {
addr: UInt<64>,
cycles_left: UInt<8>,
}
impl MemoryQueueEntry {
#[hdl]
fn default_sim(self) -> SimValue<Self> {
#[hdl(sim)]
Self {
addr: 0u64,
cycles_left: 0u8,
}
}
}
#[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 delay_sequence_index = Cell::new(0);
sim.resettable(
cd,
async |mut sim| {
sim.write(memory_interface.start.ready, false).await;
sim.write(memory_interface.finish.ready, false).await;
sim.write(
queue_debug,
queue_debug.ty().new_sim(MemoryQueueEntry.default_sim()),
)
.await;
},
|sim, ()| {
run_fn(
cd,
memory_interface,
queue_debug,
&delay_sequence_index,
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 }>>>,
delay_sequence_index: &Cell<u64>,
mut sim: ExternModuleSimulationState,
) {
let config = memory_interface.config.ty();
let mut queue: VecDeque<SimValue<MemoryQueueEntry>> = VecDeque::new();
loop {
let mut sim_queue = queue_debug.ty().new_sim(MemoryQueueEntry.default_sim());
for entry in &queue {
ArrayVec::try_push_sim(&mut sim_queue, entry)
.ok()
.expect("queue is known to be small enough");
}
sim.write(queue_debug, sim_queue).await;
todo!();
sim.wait_for_clock_edge(cd.clk).await;
println!(
"Dump mock memory queue: {:#?}",
Vec::from_iter(
queue
.iter()
.map(|v| { DebugAsDisplay(format!("addr={:#x}", v.addr.as_int())) })
)
);
}
}
}
#[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 fetch = instance(fetch(config));
#[hdl]
let fetch {
cd: fetch_cd,
memory_interface: fetch_memory_interface,
from_next_pc: fetch_from_next_pc,
} = fetch;
connect(fetch_cd, cd);
connect(fetch_from_next_pc, from_next_pc);
#[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);
}
#[hdl]
#[test]
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 = 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),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
for cycle in 0..2000 {
todo!("drive m.from_next_pc");
sim.advance_time(SimDuration::from_nanos(500));
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);
}
// 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!();
}
}