forked from libre-chip/cpu
Compare commits
2 commits
6a4881ca25
...
cbd52c60a8
| Author | SHA1 | Date | |
|---|---|---|---|
| cbd52c60a8 | |||
| c87a1b8e1e |
5 changed files with 20885 additions and 11168 deletions
|
|
@ -163,3 +163,15 @@ pub type CpuConfigFetchWidthInBytes<C: PhantomConstGet<CpuConfig>> = DynSize;
|
|||
|
||||
#[hdl(get(|c| c.rob_size.get()))]
|
||||
pub type CpuConfigRobSize<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
pub trait PhantomConstCpuConfig:
|
||||
PhantomConstGet<CpuConfig>
|
||||
+ Into<PhantomConst<CpuConfig>>
|
||||
+ From<PhantomConst<CpuConfig>>
|
||||
+ Type
|
||||
+ ToSimValue<Type = Self>
|
||||
+ ToExpr<Type = Self>
|
||||
{
|
||||
}
|
||||
|
||||
impl PhantomConstCpuConfig for PhantomConst<CpuConfig> {}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -190,6 +190,35 @@ impl<T: Type, N: Size> ArrayVec<T, N> {
|
|||
mapped_array_vec
|
||||
}
|
||||
#[hdl]
|
||||
pub fn map_sim<U: Type>(
|
||||
this: impl ToSimValue<Type = Self>,
|
||||
uninit_element: impl ToSimValue<Type = U>,
|
||||
mut f: impl FnMut(usize, SimValue<T>) -> SimValue<U>,
|
||||
) -> SimValue<ArrayVec<U, N>> {
|
||||
let this = this.into_sim_value();
|
||||
let uninit_element = uninit_element.into_sim_value();
|
||||
let ty = this.ty().mapped_ty(uninit_element.ty());
|
||||
#[hdl(sim)]
|
||||
let Self { elements, len } = this;
|
||||
#[hdl(sim)]
|
||||
ArrayVec::<_, _> {
|
||||
elements: SimValue::from_array_elements(
|
||||
ty.elements,
|
||||
SimValue::into_value(elements)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, element)| {
|
||||
if index < *len {
|
||||
f(index, element)
|
||||
} else {
|
||||
uninit_element.clone()
|
||||
}
|
||||
}),
|
||||
),
|
||||
len,
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
pub fn as_array_of_options(this: impl ToExpr<Type = Self>) -> Expr<ArrayType<HdlOption<T>, N>> {
|
||||
let this = this.to_expr();
|
||||
#[hdl]
|
||||
|
|
@ -217,3 +246,34 @@ where
|
|||
<ArrayType<T, N> as ExprIndex<Idx>>::expr_index(&this.elements, index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Type> ArrayVec<T, ConstUsize<1>> {
|
||||
#[hdl]
|
||||
pub fn from_opt_sim(
|
||||
opt: impl ToSimValue<Type = HdlOption<T>>,
|
||||
uninit_element: impl ToSimValueWithType<T>,
|
||||
) -> SimValue<Self> {
|
||||
let opt = opt.into_sim_value();
|
||||
let ty = ArrayVec[opt.ty().HdlSome][ConstUsize];
|
||||
#[hdl(sim)]
|
||||
match opt {
|
||||
HdlSome(v) => ty.new_full_sim([v]),
|
||||
HdlNone => ty.new_sim(uninit_element),
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
pub fn into_opt_sim(this: impl ToSimValue<Type = Self>) -> SimValue<HdlOption<T>> {
|
||||
let this = this.into_sim_value();
|
||||
#[hdl(sim)]
|
||||
let Self { elements, len } = this;
|
||||
let [element] = SimValue::into_value(elements);
|
||||
let ty = HdlOption[element.ty()];
|
||||
if *len == 0 {
|
||||
#[hdl(sim)]
|
||||
ty.HdlNone()
|
||||
} else {
|
||||
#[hdl(sim)]
|
||||
ty.HdlSome(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,9 +4,10 @@
|
|||
use cpu::{
|
||||
config::{CpuConfig, UnitConfig},
|
||||
next_pc::{
|
||||
DecodeToPostDecodeInterface, DecodeToPostDecodeInterfaceInner, FETCH_BLOCK_ID_WIDTH,
|
||||
NextPcToFetchInterface, NextPcToFetchInterfaceInner, WipDecodedInsn, WipDecodedInsnKind,
|
||||
next_pc,
|
||||
CallStackOp, DecodeToPostDecodeInterface, DecodeToPostDecodeInterfaceInner,
|
||||
FETCH_BLOCK_ID_WIDTH, NextPcToFetchInterface, NextPcToFetchInterfaceInner,
|
||||
PostDecodeOutputInterface, RetireToNextPcInterface, RetireToNextPcInterfaceInner,
|
||||
RetireToNextPcInterfacePerInsn, WipDecodedInsn, WipDecodedInsnKind, next_pc,
|
||||
},
|
||||
unit::UnitKind,
|
||||
util::array_vec::ArrayVec,
|
||||
|
|
@ -14,8 +15,9 @@ use cpu::{
|
|||
use fayalite::{prelude::*, sim::vcd::VcdWriterDecls, util::RcWriter};
|
||||
use std::{
|
||||
cell::Cell,
|
||||
collections::{BTreeMap, VecDeque},
|
||||
collections::{BTreeMap, BTreeSet, VecDeque},
|
||||
num::NonZeroUsize,
|
||||
u64,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
|
@ -37,6 +39,123 @@ impl MockInsn {
|
|||
MockInsn::Ret => 4,
|
||||
}
|
||||
}
|
||||
const INSNS: &'static [(u64, Self)] = &[
|
||||
(0x0, MockInsn::Nop4),
|
||||
(0x4, MockInsn::Nop4),
|
||||
(0x8, MockInsn::CondBranch { target: 0x4 }),
|
||||
(0xC, MockInsn::Call { target: 0x18 }),
|
||||
(0x10, MockInsn::Jump { target: 0x14 }),
|
||||
(0x14, MockInsn::Jump { target: 0x10 }),
|
||||
(0x18, MockInsn::Jump { target: 0x1C }),
|
||||
(0x1C, MockInsn::Ret),
|
||||
];
|
||||
const RETIRE_SEQ_INIT: &'static [RetireSeqEntry] = &[
|
||||
RetireSeqEntry {
|
||||
pc: 0x0,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Nop4,
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x4,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Nop4,
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x8,
|
||||
cond_br_taken: Some(true),
|
||||
kind: MockInsn::CondBranch { target: 0x4 },
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x4,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Nop4,
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x8,
|
||||
cond_br_taken: Some(true),
|
||||
kind: MockInsn::CondBranch { target: 0x4 },
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x4,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Nop4,
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x8,
|
||||
cond_br_taken: Some(true),
|
||||
kind: MockInsn::CondBranch { target: 0x4 },
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x4,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Nop4,
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x8,
|
||||
cond_br_taken: Some(false),
|
||||
kind: MockInsn::CondBranch { target: 0x4 },
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0xC,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Call { target: 0x18 },
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x18,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Jump { target: 0x1C },
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x1C,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Ret,
|
||||
},
|
||||
];
|
||||
const RETIRE_SEQ_CYCLE: &'static [RetireSeqEntry] = &[
|
||||
RetireSeqEntry {
|
||||
pc: 0x10,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Jump { target: 0x14 },
|
||||
},
|
||||
RetireSeqEntry {
|
||||
pc: 0x14,
|
||||
cond_br_taken: None,
|
||||
kind: MockInsn::Jump { target: 0x10 },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct RetireSeqEntry {
|
||||
pc: u64,
|
||||
cond_br_taken: Option<bool>,
|
||||
kind: MockInsn,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RetireSeq {
|
||||
iter: std::iter::Chain<
|
||||
std::slice::Iter<'static, RetireSeqEntry>,
|
||||
std::iter::Cycle<std::slice::Iter<'static, RetireSeqEntry>>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl RetireSeq {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
iter: MockInsn::RETIRE_SEQ_INIT
|
||||
.iter()
|
||||
.chain(MockInsn::RETIRE_SEQ_CYCLE.iter().cycle()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RetireSeq {
|
||||
type Item = RetireSeqEntry;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -47,16 +166,7 @@ struct MockInsns {
|
|||
impl MockInsns {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
insns: BTreeMap::from_iter([
|
||||
(0x0, MockInsn::Nop4),
|
||||
(0x4, MockInsn::Nop4),
|
||||
(0x8, MockInsn::CondBranch { target: 0x4 }),
|
||||
(0xC, MockInsn::Call { target: 0x18 }),
|
||||
(0x10, MockInsn::Jump { target: 0x10 }),
|
||||
(0x14, MockInsn::Jump { target: 0x10 }),
|
||||
(0x18, MockInsn::Jump { target: 0x1C }),
|
||||
(0x1C, MockInsn::Ret),
|
||||
]),
|
||||
insns: BTreeMap::from_iter(MockInsn::INSNS.iter().copied()),
|
||||
}
|
||||
}
|
||||
fn fetch_block(&self, pc_range: std::ops::Range<u64>) -> impl Iterator<Item = (u64, MockInsn)> {
|
||||
|
|
@ -78,7 +188,7 @@ const DEMO_ILLEGAL_INSN_TRAP: u64 = 0xFF000000u64;
|
|||
|
||||
#[hdl]
|
||||
struct FetchPipeQueueEntry {
|
||||
fetch_pc: UInt<64>,
|
||||
start_pc: UInt<64>,
|
||||
cycles_left: UInt<8>,
|
||||
fetch_block_id: UInt<{ FETCH_BLOCK_ID_WIDTH }>,
|
||||
}
|
||||
|
|
@ -88,7 +198,7 @@ impl FetchPipeQueueEntry {
|
|||
fn default_sim(self) -> SimValue<Self> {
|
||||
#[hdl(sim)]
|
||||
FetchPipeQueueEntry {
|
||||
fetch_pc: 0u64,
|
||||
start_pc: 0u64,
|
||||
cycles_left: 0u8,
|
||||
fetch_block_id: 0u8,
|
||||
}
|
||||
|
|
@ -129,7 +239,8 @@ fn mock_fetch_pipe(config: PhantomConst<CpuConfig>) {
|
|||
sim.resettable(
|
||||
cd,
|
||||
async |mut sim| {
|
||||
sim.write(from_fetch.inner.ready, false).await;
|
||||
sim.write(from_fetch.fetch.ready, false).await;
|
||||
sim.write(from_fetch.cancel.ready, false).await;
|
||||
sim.write(
|
||||
to_post_decode.inner.data,
|
||||
to_post_decode.ty().inner.data.HdlNone(),
|
||||
|
|
@ -179,21 +290,21 @@ fn mock_fetch_pipe(config: PhantomConst<CpuConfig>) {
|
|||
if let Some(front) = queue.front().filter(|v| v.cycles_left.as_int() == 0) {
|
||||
#[hdl(sim)]
|
||||
let FetchPipeQueueEntry {
|
||||
fetch_pc,
|
||||
start_pc,
|
||||
cycles_left: _,
|
||||
fetch_block_id,
|
||||
} = front;
|
||||
let fetch_pc = fetch_pc.as_int();
|
||||
let fetch_end =
|
||||
(fetch_pc + 1).next_multiple_of(config.get().fetch_width_in_bytes() as u64);
|
||||
let start_pc = start_pc.as_int();
|
||||
let end_pc =
|
||||
(start_pc + 1).next_multiple_of(config.get().fetch_width_in_bytes() as u64);
|
||||
let insns = to_post_decode.ty().inner.data.HdlSome.insns;
|
||||
let zeroed_insn = UInt[insns.element().canonical().bit_width()]
|
||||
.zero()
|
||||
.cast_bits_to(insns.element());
|
||||
let mut insns = insns.new_sim(zeroed_insn);
|
||||
let mut expected_pc = fetch_pc;
|
||||
let mut expected_pc = start_pc;
|
||||
// TODO: handle instructions that go past the end of a fetch block
|
||||
for (pc, insn) in mock_insns.fetch_block(fetch_pc..fetch_end) {
|
||||
for (pc, insn) in mock_insns.fetch_block(start_pc..end_pc) {
|
||||
let next_pc = pc + insn.byte_len();
|
||||
if pc != expected_pc {
|
||||
break;
|
||||
|
|
@ -226,7 +337,7 @@ fn mock_fetch_pipe(config: PhantomConst<CpuConfig>) {
|
|||
WipDecodedInsn {
|
||||
fetch_block_id,
|
||||
id: next_id.cast_to_static::<UInt<_>>(),
|
||||
pc: fetch_pc,
|
||||
pc: start_pc,
|
||||
size_in_bytes: 0u8.cast_to_static::<UInt<_>>(),
|
||||
kind: WipDecodedInsnKind.Interrupt(DEMO_ILLEGAL_INSN_TRAP),
|
||||
},
|
||||
|
|
@ -250,8 +361,9 @@ fn mock_fetch_pipe(config: PhantomConst<CpuConfig>) {
|
|||
)
|
||||
.await;
|
||||
}
|
||||
sim.write(from_fetch.inner.ready, queue.len() < FETCH_PIPE_QUEUE_SIZE)
|
||||
sim.write(from_fetch.fetch.ready, queue.len() < FETCH_PIPE_QUEUE_SIZE)
|
||||
.await;
|
||||
sim.write(from_fetch.cancel.ready, true).await;
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
if sim.read_past_bool(to_post_decode.inner.ready, cd.clk).await {
|
||||
#[hdl(sim)]
|
||||
|
|
@ -264,25 +376,31 @@ fn mock_fetch_pipe(config: PhantomConst<CpuConfig>) {
|
|||
entry.cycles_left = (entry.cycles_left.as_int() - 1u8).to_sim_value();
|
||||
}
|
||||
}
|
||||
if !sim.read_past_bool(from_fetch.inner.ready, cd.clk).await {
|
||||
continue;
|
||||
}
|
||||
// handle cancels before pushing new fetch op
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(inner) = sim.read_past(from_fetch.inner.data, cd.clk).await {
|
||||
#[hdl(sim)]
|
||||
let NextPcToFetchInterfaceInner {
|
||||
next_fetch_pc,
|
||||
fetch_block_id,
|
||||
in_progress_fetches_to_cancel,
|
||||
} = &inner;
|
||||
if let HdlSome(in_progress_fetches_to_cancel) =
|
||||
sim.read_past(from_fetch.cancel.data, cd.clk).await
|
||||
{
|
||||
// cancel in-progress fetches from newest to oldest
|
||||
for _ in 0..in_progress_fetches_to_cancel.as_int() {
|
||||
for _ in 0..*in_progress_fetches_to_cancel {
|
||||
let _ = queue.pop_back();
|
||||
}
|
||||
}
|
||||
if !sim.read_past_bool(from_fetch.fetch.ready, cd.clk).await {
|
||||
continue;
|
||||
}
|
||||
// handle pushing new fetch op after handling cancels
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(inner) = sim.read_past(from_fetch.fetch.data, cd.clk).await {
|
||||
#[hdl(sim)]
|
||||
let NextPcToFetchInterfaceInner {
|
||||
start_pc,
|
||||
fetch_block_id,
|
||||
} = &inner;
|
||||
queue.push_back(
|
||||
#[hdl(sim)]
|
||||
FetchPipeQueueEntry {
|
||||
fetch_pc: next_fetch_pc,
|
||||
start_pc,
|
||||
cycles_left: FetchPipeQueueEntry::get_next_delay(delay_sequence_index),
|
||||
fetch_block_id,
|
||||
},
|
||||
|
|
@ -292,18 +410,383 @@ fn mock_fetch_pipe(config: PhantomConst<CpuConfig>) {
|
|||
}
|
||||
}
|
||||
|
||||
const EXECUTE_RETIRE_PIPE_QUEUE_SIZE: usize = 15;
|
||||
|
||||
#[hdl]
|
||||
struct ExecuteRetirePipeQueueEntry {
|
||||
insn: WipDecodedInsn,
|
||||
cycles_left: UInt<8>,
|
||||
}
|
||||
|
||||
impl ExecuteRetirePipeQueueEntry {
|
||||
#[hdl]
|
||||
fn default_sim(self) -> SimValue<Self> {
|
||||
#[hdl(sim)]
|
||||
Self {
|
||||
insn: #[hdl(sim)]
|
||||
WipDecodedInsn {
|
||||
fetch_block_id: 0u8,
|
||||
id: 0_hdl_u12,
|
||||
pc: 0xEEEE_EEEE_EEEE_EEEEu64,
|
||||
size_in_bytes: 0_hdl_u4,
|
||||
kind: WipDecodedInsnKind.NonBranch(),
|
||||
},
|
||||
cycles_left: 0u8,
|
||||
}
|
||||
}
|
||||
fn get_next_delay(delay_sequence_index: &Cell<u64>) -> u8 {
|
||||
let index = delay_sequence_index.get();
|
||||
delay_sequence_index.set(delay_sequence_index.get().wrapping_add(1));
|
||||
// make a pseudo-random number deterministically based on index
|
||||
let random = index
|
||||
.wrapping_add(1)
|
||||
.wrapping_mul(0x39FF446D8BFB75BB) // random prime
|
||||
.rotate_left(32)
|
||||
.wrapping_mul(0x73161B54984B1C21) // random prime
|
||||
.rotate_right(60);
|
||||
(random % 16) as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// an arbitrary value
|
||||
const END_PC: u64 = u64::from_be_bytes(*b"EndInsns");
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MockExecuteState {
|
||||
queue: VecDeque<SimValue<ExecuteRetirePipeQueueEntry>>,
|
||||
used_ids: BTreeSet<SimValue<UInt<12>>>,
|
||||
retire_seq: RetireSeq,
|
||||
config: PhantomConst<CpuConfig>,
|
||||
}
|
||||
|
||||
impl MockExecuteState {
|
||||
fn new(config: PhantomConst<CpuConfig>) -> Self {
|
||||
Self {
|
||||
queue: VecDeque::new(),
|
||||
used_ids: BTreeSet::new(),
|
||||
retire_seq: RetireSeq::new(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
fn on_clock_cycle(&mut self) {
|
||||
for entry in &mut self.queue {
|
||||
if entry.cycles_left.as_int() > 0 {
|
||||
entry.cycles_left = (entry.cycles_left.as_int() - 1u8).to_sim_value();
|
||||
}
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
fn do_retire(
|
||||
&mut self,
|
||||
entry: SimValue<ExecuteRetirePipeQueueEntry>,
|
||||
) -> Result<SimValue<RetireToNextPcInterfacePerInsn<PhantomConst<CpuConfig>>>, String> {
|
||||
#[hdl(sim)]
|
||||
let ExecuteRetirePipeQueueEntry {
|
||||
insn,
|
||||
cycles_left: _,
|
||||
} = entry;
|
||||
self.used_ids.remove(&insn.id);
|
||||
let RetireSeqEntry {
|
||||
pc,
|
||||
cond_br_taken,
|
||||
kind,
|
||||
} = self
|
||||
.retire_seq
|
||||
.next()
|
||||
.ok_or_else(|| "expected no more instructions to retire")?;
|
||||
let next_pc = self
|
||||
.retire_seq
|
||||
.clone()
|
||||
.next()
|
||||
.map(|v| v.pc)
|
||||
.unwrap_or(END_PC);
|
||||
let (expected_kind, call_stack_op) = match kind {
|
||||
MockInsn::Nop4 => (
|
||||
#[hdl(sim)]
|
||||
WipDecodedInsnKind::NonBranch(),
|
||||
#[hdl(sim)]
|
||||
CallStackOp::None(),
|
||||
),
|
||||
MockInsn::Jump { target } => (
|
||||
#[hdl(sim)]
|
||||
WipDecodedInsnKind::Branch(target),
|
||||
#[hdl(sim)]
|
||||
CallStackOp::None(),
|
||||
),
|
||||
MockInsn::CondBranch { target } => (
|
||||
#[hdl(sim)]
|
||||
WipDecodedInsnKind::BranchCond(target),
|
||||
#[hdl(sim)]
|
||||
CallStackOp::None(),
|
||||
),
|
||||
MockInsn::Call { target } => (
|
||||
#[hdl(sim)]
|
||||
WipDecodedInsnKind::Call(target),
|
||||
#[hdl(sim)]
|
||||
CallStackOp::Push(pc.wrapping_add(kind.byte_len())),
|
||||
),
|
||||
MockInsn::Ret => (
|
||||
#[hdl(sim)]
|
||||
WipDecodedInsnKind::Ret(),
|
||||
#[hdl(sim)]
|
||||
CallStackOp::Pop(),
|
||||
),
|
||||
};
|
||||
let expected_insn = #[hdl(sim)]
|
||||
WipDecodedInsn {
|
||||
fetch_block_id: &insn.fetch_block_id,
|
||||
id: &insn.id,
|
||||
pc,
|
||||
size_in_bytes: kind.byte_len().cast_to_static::<UInt<4>>(),
|
||||
kind: expected_kind,
|
||||
};
|
||||
if *expected_insn.cmp_ne(&insn) {
|
||||
return Err(format!(
|
||||
"insn doesn't match expected:\ninsn: {insn:?}\nexpected insn: {expected_insn:?}"
|
||||
));
|
||||
}
|
||||
Ok(
|
||||
#[hdl(sim)]
|
||||
RetireToNextPcInterfacePerInsn::<_> {
|
||||
id: &insn.id,
|
||||
next_pc,
|
||||
call_stack_op,
|
||||
cond_br_taken: if let Some(cond_br_taken) = cond_br_taken {
|
||||
#[hdl(sim)]
|
||||
HdlSome(cond_br_taken)
|
||||
} else {
|
||||
#[hdl(sim)]
|
||||
HdlNone()
|
||||
},
|
||||
config: self.config,
|
||||
},
|
||||
)
|
||||
}
|
||||
#[hdl]
|
||||
fn try_retire(
|
||||
&mut self,
|
||||
) -> Option<Result<SimValue<RetireToNextPcInterfacePerInsn<PhantomConst<CpuConfig>>>, String>>
|
||||
{
|
||||
if self.queue.front()?.cycles_left.as_int() != 0 {
|
||||
return None;
|
||||
}
|
||||
let entry = self.queue.pop_front()?;
|
||||
Some(self.do_retire(entry))
|
||||
}
|
||||
fn space_available(&self) -> usize {
|
||||
EXECUTE_RETIRE_PIPE_QUEUE_SIZE.saturating_sub(self.queue.len())
|
||||
}
|
||||
#[hdl]
|
||||
fn start(&mut self, insn: &SimValue<WipDecodedInsn>, delay_sequence_index: &Cell<u64>) {
|
||||
if !self.used_ids.insert(insn.id.clone()) {
|
||||
panic!("next_pc gave a duplicate insn id: {insn:?}");
|
||||
}
|
||||
self.queue.push_back(
|
||||
#[hdl(sim)]
|
||||
ExecuteRetirePipeQueueEntry {
|
||||
insn,
|
||||
cycles_left: ExecuteRetirePipeQueueEntry::get_next_delay(delay_sequence_index),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn mock_execute_retire_pipe(config: PhantomConst<CpuConfig>) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let from_post_decode: PostDecodeOutputInterface<PhantomConst<CpuConfig>> =
|
||||
m.input(PostDecodeOutputInterface[config]);
|
||||
#[hdl]
|
||||
let retire_output: RetireToNextPcInterface<PhantomConst<CpuConfig>> =
|
||||
m.output(RetireToNextPcInterface[config]);
|
||||
#[hdl]
|
||||
let queue_debug: ArrayVec<
|
||||
ExecuteRetirePipeQueueEntry,
|
||||
ConstUsize<{ EXECUTE_RETIRE_PIPE_QUEUE_SIZE }>,
|
||||
> = m.output();
|
||||
m.register_clock_for_past(cd.clk);
|
||||
m.extern_module_simulation_fn(
|
||||
(cd, from_post_decode, retire_output, queue_debug),
|
||||
|(cd, from_post_decode, retire_output, 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(from_post_decode.ready, 0usize).await;
|
||||
sim.write(
|
||||
retire_output.inner.data,
|
||||
retire_output.ty().inner.data.HdlNone(),
|
||||
)
|
||||
.await;
|
||||
sim.write(
|
||||
queue_debug,
|
||||
queue_debug
|
||||
.ty()
|
||||
.new_sim(ExecuteRetirePipeQueueEntry.default_sim()),
|
||||
)
|
||||
.await;
|
||||
},
|
||||
|sim, ()| {
|
||||
run_fn(
|
||||
cd,
|
||||
from_post_decode,
|
||||
retire_output,
|
||||
queue_debug,
|
||||
&delay_sequence_index,
|
||||
sim,
|
||||
)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
},
|
||||
);
|
||||
#[hdl]
|
||||
async fn run_fn(
|
||||
cd: Expr<ClockDomain>,
|
||||
from_post_decode: Expr<PostDecodeOutputInterface<PhantomConst<CpuConfig>>>,
|
||||
retire_output: Expr<RetireToNextPcInterface<PhantomConst<CpuConfig>>>,
|
||||
queue_debug: Expr<
|
||||
ArrayVec<ExecuteRetirePipeQueueEntry, ConstUsize<{ EXECUTE_RETIRE_PIPE_QUEUE_SIZE }>>,
|
||||
>,
|
||||
delay_sequence_index: &Cell<u64>,
|
||||
mut sim: ExternModuleSimulationState,
|
||||
) {
|
||||
let config = from_post_decode.config.ty();
|
||||
let mut state = MockExecuteState::new(config);
|
||||
let empty_retire_insn = #[hdl(sim)]
|
||||
RetireToNextPcInterfacePerInsn::<_> {
|
||||
id: 0_hdl_u12,
|
||||
next_pc: 0u64,
|
||||
call_stack_op: #[hdl(sim)]
|
||||
CallStackOp::None(),
|
||||
cond_br_taken: #[hdl(sim)]
|
||||
HdlNone(),
|
||||
config,
|
||||
};
|
||||
let retire_vec_ty = retire_output.inner.data.ty().HdlSome.insns;
|
||||
loop {
|
||||
state.on_clock_cycle();
|
||||
let mut sim_queue = queue_debug
|
||||
.ty()
|
||||
.new_sim(ExecuteRetirePipeQueueEntry.default_sim());
|
||||
for entry in &state.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;
|
||||
let mut retiring = retire_vec_ty.new_sim(&empty_retire_insn);
|
||||
let mut peek_state = state.clone();
|
||||
while let Some(peek_retire) = peek_state.try_retire() {
|
||||
if peek_retire.is_err() && **ArrayVec::len_sim(&retiring) > 0 {
|
||||
break;
|
||||
}
|
||||
let peek_retire = peek_retire.unwrap_or_else(|_| {
|
||||
#[hdl(sim)]
|
||||
RetireToNextPcInterfacePerInsn::<_> {
|
||||
id: 0_hdl_u12,
|
||||
next_pc: u64::from_be_bytes(*b"ErrError"),
|
||||
call_stack_op: #[hdl(sim)]
|
||||
CallStackOp::None(),
|
||||
cond_br_taken: #[hdl(sim)]
|
||||
HdlNone(),
|
||||
config,
|
||||
}
|
||||
});
|
||||
let Ok(_) = ArrayVec::try_push_sim(&mut retiring, peek_retire) else {
|
||||
break;
|
||||
};
|
||||
}
|
||||
sim.write(
|
||||
retire_output.inner.data,
|
||||
if **ArrayVec::len_sim(&retiring) > 0 {
|
||||
#[hdl(sim)]
|
||||
(retire_output.inner.data.ty()).HdlSome(
|
||||
#[hdl(sim)]
|
||||
RetireToNextPcInterfaceInner::<_> {
|
||||
insns: retiring,
|
||||
config,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
#[hdl(sim)]
|
||||
(retire_output.inner.data.ty()).HdlNone()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
sim.write(
|
||||
from_post_decode.ready,
|
||||
state.space_available().min(config.get().fetch_width.get()),
|
||||
)
|
||||
.await;
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
if sim.read_past_bool(retire_output.inner.ready, cd.clk).await {
|
||||
for _ in 0..**ArrayVec::len_sim(&retiring) {
|
||||
match state.try_retire() {
|
||||
Some(Ok(_)) => {}
|
||||
Some(Err(e)) => panic!("retire error: {e}"),
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut new_insns = sim.read_past(from_post_decode.insns, cd.clk).await;
|
||||
ArrayVec::truncate_sim(
|
||||
&mut new_insns,
|
||||
*sim.read_past(from_post_decode.ready, cd.clk).await,
|
||||
);
|
||||
for insn in ArrayVec::elements_sim_ref(&new_insns) {
|
||||
state.start(insn, delay_sequence_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
fn dut(config: PhantomConst<CpuConfig>) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let next_pc = instance(next_pc(config));
|
||||
connect(next_pc.cd, cd);
|
||||
#[hdl]
|
||||
let next_pc {
|
||||
cd: next_pc_cd,
|
||||
to_fetch: next_pc_to_fetch,
|
||||
from_decode: next_pc_from_decode,
|
||||
post_decode_output: next_pc_post_decode_output,
|
||||
from_retire: next_pc_from_retire,
|
||||
state_for_debug: _,
|
||||
} = next_pc;
|
||||
connect(next_pc_cd, cd);
|
||||
#[hdl]
|
||||
let mock_fetch_pipe = instance(mock_fetch_pipe(config));
|
||||
connect(mock_fetch_pipe.cd, cd);
|
||||
connect(mock_fetch_pipe.from_fetch, next_pc.to_fetch);
|
||||
connect(next_pc.from_decode, mock_fetch_pipe.to_post_decode);
|
||||
#[hdl]
|
||||
let mock_fetch_pipe {
|
||||
cd: mock_fetch_pipe_cd,
|
||||
from_fetch: mock_fetch_pipe_from_fetch,
|
||||
to_post_decode: mock_fetch_pipe_to_post_decode,
|
||||
queue_debug: _,
|
||||
} = mock_fetch_pipe;
|
||||
connect(mock_fetch_pipe_cd, cd);
|
||||
connect(mock_fetch_pipe_from_fetch, next_pc_to_fetch);
|
||||
connect(next_pc_from_decode, mock_fetch_pipe_to_post_decode);
|
||||
#[hdl]
|
||||
let mock_execute_retire_pipe = instance(mock_execute_retire_pipe(config));
|
||||
#[hdl]
|
||||
let mock_execute_retire_pipe {
|
||||
cd: mock_execute_retire_pipe_cd,
|
||||
from_post_decode: mock_execute_retire_pipe_from_post_decode,
|
||||
retire_output: mock_execute_retire_pipe_retire_output,
|
||||
queue_debug: _,
|
||||
} = mock_execute_retire_pipe;
|
||||
connect(mock_execute_retire_pipe_cd, cd);
|
||||
connect(next_pc_from_retire, mock_execute_retire_pipe_retire_output);
|
||||
connect(
|
||||
mock_execute_retire_pipe_from_post_decode,
|
||||
next_pc_post_decode_output,
|
||||
);
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
|
|
@ -322,6 +805,20 @@ fn test_next_pc() {
|
|||
let mut sim = Simulation::new(m);
|
||||
let mut 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..300 {
|
||||
|
|
@ -332,7 +829,7 @@ fn test_next_pc() {
|
|||
sim.write_reset(sim.io().cd.rst, false);
|
||||
}
|
||||
// FIXME: vcd is just whatever next_pc does now, which isn't known to be correct
|
||||
let vcd = String::from_utf8(writer.take()).unwrap();
|
||||
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
if vcd != include_str!("expected/next_pc.vcd") {
|
||||
panic!();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue