forked from libre-chip/cpu
Compare commits
10 commits
4ffaba8840
...
a0ae4ec74c
| Author | SHA1 | Date | |
|---|---|---|---|
| a0ae4ec74c | |||
| 6ed04c809e | |||
| a1147f0f05 | |||
| 5bdc71acc3 | |||
| a15367c37e | |||
| 0d451e4e95 | |||
| 689b4ef65a | |||
| 79eac8929a | |||
| f372190b68 | |||
| e31375b9ce |
25 changed files with 696322 additions and 200406 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -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#2aa41137d4eec592bd591fddb558bc78a52c635c"
|
||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#80b92c7dd3adcad8487a3f8224846e381219bfa6"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bitvec",
|
||||
|
|
@ -417,7 +417,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "fayalite-proc-macros"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#2aa41137d4eec592bd591fddb558bc78a52c635c"
|
||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#80b92c7dd3adcad8487a3f8224846e381219bfa6"
|
||||
dependencies = [
|
||||
"fayalite-proc-macros-impl",
|
||||
]
|
||||
|
|
@ -425,7 +425,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#2aa41137d4eec592bd591fddb558bc78a52c635c"
|
||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#80b92c7dd3adcad8487a3f8224846e381219bfa6"
|
||||
dependencies = [
|
||||
"base16ct 0.2.0",
|
||||
"num-bigint",
|
||||
|
|
@ -440,7 +440,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "fayalite-visit-gen"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#2aa41137d4eec592bd591fddb558bc78a52c635c"
|
||||
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#80b92c7dd3adcad8487a3f8224846e381219bfa6"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
|
|
|
|||
|
|
@ -33,3 +33,6 @@ hex-literal.workspace = true
|
|||
regex = "1.12.2"
|
||||
sha2.workspace = true
|
||||
which.workspace = true
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(todo)'] }
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
use crate::{
|
||||
instruction::{CONST_ZERO_UNIT_NUM, MOpTrait, PRegNum, RenamedMOp, UnitNum, UnitOutRegNum},
|
||||
unit::{
|
||||
UnitCancelInput, UnitKind, UnitOutputWrite,
|
||||
unit_base::{UnitForwardingInfo, UnitToRegAlloc},
|
||||
},
|
||||
};
|
||||
use crate::{instruction::CONST_ZERO_UNIT_NUM, unit::UnitKind};
|
||||
use fayalite::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::num::NonZeroUsize;
|
||||
|
|
@ -101,55 +95,14 @@ impl CpuConfig {
|
|||
pub fn unit_num_width(&self) -> usize {
|
||||
UInt::range(CONST_ZERO_UNIT_NUM..self.non_const_unit_nums().end).width()
|
||||
}
|
||||
pub fn unit_num(&self) -> UnitNum<DynSize> {
|
||||
UnitNum[self.unit_num_width()]
|
||||
}
|
||||
pub fn unit_out_reg_num(&self) -> UnitOutRegNum<DynSize> {
|
||||
UnitOutRegNum[self.out_reg_num_width]
|
||||
}
|
||||
pub fn p_reg_num(&self) -> PRegNum<DynSize, DynSize> {
|
||||
PRegNum[self.unit_num_width()][self.out_reg_num_width]
|
||||
}
|
||||
pub fn p_reg_num_width(&self) -> usize {
|
||||
self.unit_num_width() + self.out_reg_num_width
|
||||
}
|
||||
pub fn renamed_mop_in_unit(&self) -> RenamedMOp<UnitOutRegNum<DynSize>, DynSize> {
|
||||
RenamedMOp[self.unit_out_reg_num()][self.p_reg_num_width()]
|
||||
}
|
||||
pub fn unit_output_write(&self) -> UnitOutputWrite<DynSize> {
|
||||
UnitOutputWrite[self.out_reg_num_width]
|
||||
}
|
||||
pub fn unit_output_writes(&self) -> Array<HdlOption<UnitOutputWrite<DynSize>>> {
|
||||
Array[HdlOption[self.unit_output_write()]][self.non_const_unit_nums().len()]
|
||||
}
|
||||
pub fn unit_cancel_input(&self) -> UnitCancelInput<DynSize> {
|
||||
UnitCancelInput[self.out_reg_num_width]
|
||||
}
|
||||
pub fn unit_forwarding_info(&self) -> UnitForwardingInfo<DynSize, DynSize, DynSize> {
|
||||
UnitForwardingInfo[self.unit_num_width()][self.out_reg_num_width]
|
||||
[self.non_const_unit_nums().len()]
|
||||
}
|
||||
pub fn unit_max_in_flight(&self, unit_index: usize) -> NonZeroUsize {
|
||||
self.units[unit_index]
|
||||
.max_in_flight
|
||||
.unwrap_or(self.default_unit_max_in_flight)
|
||||
}
|
||||
pub fn unit_to_reg_alloc<
|
||||
MOp: Type + MOpTrait<DestReg = UnitOutRegNum<DynSize>, SrcRegWidth = DynSize>,
|
||||
ExtraOut: Type,
|
||||
>(
|
||||
&self,
|
||||
mop_ty: MOp,
|
||||
extra_out_ty: ExtraOut,
|
||||
) -> UnitToRegAlloc<MOp, ExtraOut, DynSize, DynSize, DynSize> {
|
||||
assert_eq!(
|
||||
mop_ty.dest_reg_ty(),
|
||||
self.unit_out_reg_num(),
|
||||
"inconsistent types",
|
||||
);
|
||||
UnitToRegAlloc[mop_ty][extra_out_ty][self.unit_num_width()][self.out_reg_num_width]
|
||||
[self.non_const_unit_nums().len()]
|
||||
}
|
||||
pub fn fetch_width_in_bytes(&self) -> usize {
|
||||
1usize
|
||||
.checked_shl(self.log2_fetch_width_in_bytes.into())
|
||||
|
|
@ -188,6 +141,15 @@ impl CpuConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[hdl(get(|c| c.out_reg_num_width))]
|
||||
pub type CpuConfigOutRegNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.unit_num_width()))]
|
||||
pub type CpuConfigUnitNumWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.non_const_unit_nums().len()))]
|
||||
pub type CpuConfigUnitCount<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
#[hdl(get(|c| c.fetch_width.get()))]
|
||||
pub type CpuConfigFetchWidth<C: PhantomConstGet<CpuConfig>> = DynSize;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ use crate::{
|
|||
CpuConfigMaxFetchesInFlight, PhantomConstCpuConfig,
|
||||
},
|
||||
main_memory_and_io::{
|
||||
MemoryInterface, MemoryOperationErrorKind, MemoryOperationFinish,
|
||||
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
|
||||
CpuConfigFetchMemoryInterfaceConfig, MemoryInterface, MemoryInterfaceConfig,
|
||||
MemoryOperationErrorKind, MemoryOperationFinish, MemoryOperationFinishKind,
|
||||
MemoryOperationKind, MemoryOperationStart,
|
||||
},
|
||||
next_pc::{
|
||||
FETCH_BLOCK_ID_WIDTH, NextPcToFetchInterface, NextPcToFetchInterfaceInner, ResetStatus,
|
||||
|
|
@ -415,7 +416,9 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
|
|||
self.state_expr.ty().config
|
||||
}
|
||||
#[hdl]
|
||||
fn try_start_memory_operation(&mut self) -> Option<SimValue<MemoryOperationStart<C>>> {
|
||||
fn try_start_memory_operation(
|
||||
&mut self,
|
||||
) -> Option<SimValue<MemoryOperationStart<CpuConfigFetchMemoryInterfaceConfig<C>>>> {
|
||||
let config = self.config();
|
||||
for cache_miss in &mut self.cache_misses {
|
||||
let Some(next_start_fetch_block) = CacheMiss::next_start_fetch_block(cache_miss) else {
|
||||
|
|
@ -430,7 +433,7 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
|
|||
error: _, // handled by CacheMiss::next_start_fetch_block()
|
||||
config: _,
|
||||
} = cache_miss;
|
||||
let mem_op_ty = MemoryOperationStart[config];
|
||||
let mem_op_ty = MemoryOperationStart[CpuConfigFetchMemoryInterfaceConfig[config]];
|
||||
let mut addr = SplitAddr[config].split_addr_sim(addr);
|
||||
#[hdl(sim)]
|
||||
let SplitAddr::<_> {
|
||||
|
|
@ -453,8 +456,8 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
|
|||
mem_op_ty.write_data.len(),
|
||||
),
|
||||
rw_mask: repeat(true, mem_op_ty.write_data.len()),
|
||||
fetch_block_id,
|
||||
config,
|
||||
op_id: SimValue::into_dyn_int(fetch_block_id.clone()),
|
||||
config: CpuConfigFetchMemoryInterfaceConfig[config],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -497,7 +500,9 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
|
|||
fn do_memory_operation_finish<'a>(
|
||||
&mut self,
|
||||
ready_for_memory_operation_finish: ReadyForMemoryOperationFinish,
|
||||
memory_operation_finish: impl ToSimValue<Type = MemoryOperationFinish<C>>,
|
||||
memory_operation_finish: impl ToSimValue<
|
||||
Type = MemoryOperationFinish<CpuConfigFetchMemoryInterfaceConfig<C>>,
|
||||
>,
|
||||
) -> Option<SimValue<WriteBackStep<C>>> {
|
||||
let config = self.config();
|
||||
let cache_miss = &mut self.cache_misses[ready_for_memory_operation_finish.cache_miss_index];
|
||||
|
|
@ -621,9 +626,7 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
|
|||
#[hdl]
|
||||
fn check_memory_next_fetch_block_ids(
|
||||
&self,
|
||||
memory_next_fetch_block_ids: SimValue<
|
||||
ArrayVec<UInt<{ FETCH_BLOCK_ID_WIDTH }>, CpuConfigMaxFetchesInFlight<C>>,
|
||||
>,
|
||||
memory_next_fetch_block_ids: SimValue<ArrayVec<UInt, DynSize>>,
|
||||
) {
|
||||
let memory_next_fetch_block_ids = ArrayVec::elements_sim_ref(&memory_next_fetch_block_ids);
|
||||
let mut expected_memory_next_fetch_block_ids = Vec::new();
|
||||
|
|
@ -1071,8 +1074,8 @@ 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]);
|
||||
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
|
||||
m.output(MemoryInterface[CpuConfigFetchMemoryInterfaceConfig[config]]);
|
||||
#[hdl]
|
||||
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
|
||||
m.input(NextPcToFetchInterface[config]);
|
||||
|
|
@ -1103,7 +1106,7 @@ fn l1_i_cache_impl(config: PhantomConst<CpuConfig>) {
|
|||
async fn run(
|
||||
mut sim: ExternModuleSimulationState,
|
||||
cd: Expr<ClockDomain>,
|
||||
memory_interface: Expr<MemoryInterface<PhantomConst<CpuConfig>>>,
|
||||
memory_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
|
||||
from_next_pc: Expr<NextPcToFetchInterface<PhantomConst<CpuConfig>>>,
|
||||
to_decode_fetched: Expr<ReadyValid<FetchToDecodeInterfaceInner<PhantomConst<CpuConfig>>>>,
|
||||
to_decode_next_fetch_block_ids: Expr<
|
||||
|
|
@ -1196,9 +1199,8 @@ fn l1_i_cache_impl(config: PhantomConst<CpuConfig>) {
|
|||
)
|
||||
.await;
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(next_fetch_block_ids) = sim
|
||||
.read_past(memory_interface.next_fetch_block_ids, cd.clk)
|
||||
.await
|
||||
if let HdlSome(next_fetch_block_ids) =
|
||||
sim.read_past(memory_interface.next_op_ids, cd.clk).await
|
||||
{
|
||||
state.check_memory_next_fetch_block_ids(next_fetch_block_ids);
|
||||
}
|
||||
|
|
@ -1359,7 +1361,7 @@ fn l1_i_cache_impl(config: PhantomConst<CpuConfig>) {
|
|||
state_for_debug,
|
||||
),
|
||||
mut sim| async move {
|
||||
let config = memory_interface.ty().config;
|
||||
let config = from_next_pc.ty().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(
|
||||
|
|
@ -1426,8 +1428,8 @@ 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]);
|
||||
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
|
||||
m.output(MemoryInterface[CpuConfigFetchMemoryInterfaceConfig[config]]);
|
||||
#[hdl]
|
||||
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
|
||||
m.input(NextPcToFetchInterface[config]);
|
||||
|
|
@ -1524,8 +1526,8 @@ pub fn fetch(config: PhantomConst<CpuConfig>) {
|
|||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
|
||||
m.output(MemoryInterface[config]);
|
||||
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
|
||||
m.output(MemoryInterface[CpuConfigFetchMemoryInterfaceConfig[config]]);
|
||||
#[hdl]
|
||||
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
|
||||
m.input(NextPcToFetchInterface[config]);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
use crate::{
|
||||
config::{CpuConfig, CpuConfigOutRegNumWidth, CpuConfigUnitNumWidth, PhantomConstCpuConfig},
|
||||
register::{PRegFlags, PRegFlagsViewTrait, PRegValue, ViewUnused},
|
||||
unit::UnitMOp,
|
||||
util::{Rotate, range_u32_len},
|
||||
|
|
@ -2595,19 +2596,21 @@ impl<DestReg: Type, SrcRegWidth: Size> MoveRegMOp<DestReg, SrcRegWidth> {
|
|||
}
|
||||
}
|
||||
|
||||
#[hdl(cmp_eq)]
|
||||
#[hdl(cmp_eq, no_static)]
|
||||
/// there may be more than one unit of a given kind, so UnitNum is not the same as UnitKind.
|
||||
/// zero is used for built-in constants, such as the zero register
|
||||
pub struct UnitNum<Width: Size> {
|
||||
pub adj_value: UIntType<Width>,
|
||||
pub struct UnitNum<C: PhantomConstGet<CpuConfig>> {
|
||||
pub adj_value: UIntType<CpuConfigUnitNumWidth<C>>,
|
||||
pub config: C,
|
||||
}
|
||||
|
||||
impl<Width: Size> UnitNum<Width> {
|
||||
impl<C: PhantomConstCpuConfig> UnitNum<C> {
|
||||
#[hdl]
|
||||
pub fn const_zero(self) -> Expr<Self> {
|
||||
#[hdl]
|
||||
UnitNum {
|
||||
adj_value: CONST_ZERO_UNIT_NUM.cast_to(self.adj_value),
|
||||
config: self.config,
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
|
|
@ -2615,6 +2618,7 @@ impl<Width: Size> UnitNum<Width> {
|
|||
#[hdl]
|
||||
UnitNum {
|
||||
adj_value: (index + 1).cast_to(self.adj_value),
|
||||
config: self.config,
|
||||
}
|
||||
}
|
||||
pub fn is_index(expr: impl ToExpr<Type = Self>, index: usize) -> Expr<Bool> {
|
||||
|
|
@ -2622,7 +2626,9 @@ impl<Width: Size> UnitNum<Width> {
|
|||
expr.ty().from_index(index).adj_value.cmp_eq(expr.adj_value)
|
||||
}
|
||||
#[hdl]
|
||||
pub fn as_index(expr: impl ToExpr<Type = Self>) -> Expr<HdlOption<UIntType<Width>>> {
|
||||
pub fn as_index(
|
||||
expr: impl ToExpr<Type = Self>,
|
||||
) -> Expr<HdlOption<UIntType<CpuConfigUnitNumWidth<C>>>> {
|
||||
let expr = expr.to_expr();
|
||||
#[hdl]
|
||||
let unit_index = wire(HdlOption[expr.ty().adj_value]);
|
||||
|
|
@ -2640,19 +2646,20 @@ impl<Width: Size> UnitNum<Width> {
|
|||
|
||||
pub const CONST_ZERO_UNIT_NUM: usize = 0;
|
||||
|
||||
#[hdl(cmp_eq)]
|
||||
pub struct UnitOutRegNum<Width: Size> {
|
||||
pub value: UIntType<Width>,
|
||||
#[hdl(cmp_eq, no_static)]
|
||||
pub struct UnitOutRegNum<C: PhantomConstGet<CpuConfig>> {
|
||||
pub value: UIntType<CpuConfigOutRegNumWidth<C>>,
|
||||
pub config: C,
|
||||
}
|
||||
|
||||
#[hdl(cmp_eq)]
|
||||
#[hdl(cmp_eq, no_static)]
|
||||
/// Physical Register Number -- registers in the CPU's backend
|
||||
pub struct PRegNum<UnitNumWidth: Size, OutRegNumWidth: Size> {
|
||||
pub unit_num: UnitNum<UnitNumWidth>,
|
||||
pub unit_out_reg: UnitOutRegNum<OutRegNumWidth>,
|
||||
pub struct PRegNum<C: PhantomConstGet<CpuConfig>> {
|
||||
pub unit_num: UnitNum<C>,
|
||||
pub unit_out_reg: UnitOutRegNum<C>,
|
||||
}
|
||||
|
||||
impl<UnitNumWidth: Size, OutRegNumWidth: Size> PRegNum<UnitNumWidth, OutRegNumWidth> {
|
||||
impl<C: PhantomConstCpuConfig> PRegNum<C> {
|
||||
#[hdl]
|
||||
pub fn const_zero(self) -> Expr<Self> {
|
||||
#[hdl]
|
||||
|
|
@ -2661,6 +2668,7 @@ impl<UnitNumWidth: Size, OutRegNumWidth: Size> PRegNum<UnitNumWidth, OutRegNumWi
|
|||
unit_out_reg: #[hdl]
|
||||
UnitOutRegNum {
|
||||
value: 0u8.cast_to(self.unit_out_reg.value),
|
||||
config: self.unit_out_reg.config,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ pub mod instruction;
|
|||
pub mod main_memory_and_io;
|
||||
pub mod next_pc;
|
||||
pub mod powerisa_instructions_xml;
|
||||
#[cfg(todo)]
|
||||
pub mod reg_alloc;
|
||||
pub mod register;
|
||||
pub mod rename_execute_retire;
|
||||
pub mod unit;
|
||||
pub mod util;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
618
crates/cpu/src/main_memory_and_io/simple_uart.rs
Normal file
618
crates/cpu/src/main_memory_and_io/simple_uart.rs
Normal file
|
|
@ -0,0 +1,618 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use std::num::{NonZeroU64, NonZeroUsize, Wrapping};
|
||||
|
||||
use crate::{
|
||||
main_memory_and_io::{
|
||||
AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind,
|
||||
MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind,
|
||||
MemoryOperationStart,
|
||||
},
|
||||
util::array_vec::ArrayVec,
|
||||
};
|
||||
use fayalite::{
|
||||
int::UIntInRange,
|
||||
prelude::*,
|
||||
util::ready_valid::{ReadyValid, queue},
|
||||
};
|
||||
|
||||
const TICKS_PER_BAUD: usize = 16;
|
||||
const PRECISION_BITS: usize = 16;
|
||||
|
||||
#[hdl_module]
|
||||
pub fn uart_clock_gen(
|
||||
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
|
||||
baud_rate: f64,
|
||||
) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let tick: Bool = m.output();
|
||||
|
||||
let divisor =
|
||||
clock_input_properties.get().frequency.into_inner() / (baud_rate * TICKS_PER_BAUD as f64);
|
||||
type NumeratorType = u128;
|
||||
let numerator: NumeratorType = 1 << PRECISION_BITS;
|
||||
let denominator: NumeratorType = (divisor * numerator as f64).round() as _;
|
||||
let remainder_ty = UInt::range(0..denominator);
|
||||
|
||||
#[hdl]
|
||||
let remainder_reg = reg_builder().clock_domain(cd).reset(remainder_ty.zero());
|
||||
|
||||
#[hdl]
|
||||
let sum = wire((remainder_reg + numerator).ty());
|
||||
connect_any(sum, remainder_reg + numerator);
|
||||
|
||||
#[hdl]
|
||||
let tick_reg = reg_builder().clock_domain(cd).reset(false);
|
||||
connect(tick_reg, false);
|
||||
|
||||
#[hdl]
|
||||
let next_remainder = wire(remainder_ty);
|
||||
connect(remainder_reg, next_remainder);
|
||||
|
||||
#[hdl]
|
||||
if sum.cmp_ge(denominator) {
|
||||
connect_any(next_remainder, sum - denominator);
|
||||
connect(tick_reg, true);
|
||||
} else {
|
||||
connect(next_remainder, sum);
|
||||
}
|
||||
|
||||
connect(tick, tick_reg);
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
pub fn transmitter() {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let tick: Bool = m.input();
|
||||
#[hdl]
|
||||
let tx: Bool = m.output();
|
||||
#[hdl]
|
||||
let input_byte: ReadyValid<UInt<8>> = m.input();
|
||||
|
||||
#[hdl]
|
||||
let not_tx_reg: Bool = reg_builder().clock_domain(cd).reset(false);
|
||||
|
||||
connect(tx, !not_tx_reg);
|
||||
|
||||
#[hdl]
|
||||
let input_buf_reg: HdlOption<UInt<8>> = reg_builder().clock_domain(cd).reset(HdlNone());
|
||||
|
||||
const BYTE_ON_WIRE_WIDTH: usize = 10;
|
||||
|
||||
#[hdl]
|
||||
let shift_reg: Array<HdlOption<Bool>, _> = reg_builder()
|
||||
.clock_domain(cd)
|
||||
.reset([HdlNone(); BYTE_ON_WIRE_WIDTH]);
|
||||
|
||||
#[hdl]
|
||||
if let HdlSome(v) = shift_reg[0] {
|
||||
connect(not_tx_reg, !v);
|
||||
} else {
|
||||
connect(not_tx_reg, false);
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
let tick_count_reg = reg_builder()
|
||||
.clock_domain(cd)
|
||||
.reset(0u8.cast_to_static::<UIntInRange<0, { TICKS_PER_BAUD }>>());
|
||||
|
||||
#[hdl]
|
||||
let next_tick_count = wire(tick_count_reg.ty());
|
||||
|
||||
connect(tick_count_reg, next_tick_count);
|
||||
|
||||
#[hdl]
|
||||
if !tick {
|
||||
connect(next_tick_count, tick_count_reg);
|
||||
} else if tick_count_reg.cmp_ge(TICKS_PER_BAUD - 1) {
|
||||
connect(next_tick_count, 0u8.cast_to(next_tick_count.ty()));
|
||||
} else {
|
||||
connect(
|
||||
next_tick_count,
|
||||
(tick_count_reg.cast_to(UInt[tick_count_reg.ty().bit_width()]) + 1u8)
|
||||
.cast_to(next_tick_count.ty()),
|
||||
);
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
if tick & tick_count_reg.cmp_eq(0u8) {
|
||||
#[hdl]
|
||||
if let HdlSome(_) = shift_reg[0] {
|
||||
for v in shift_reg.windows(2) {
|
||||
connect(v[0], v[1]);
|
||||
}
|
||||
connect(shift_reg.last().expect("known to be non-empty"), HdlNone());
|
||||
}
|
||||
#[hdl]
|
||||
if let HdlNone = shift_reg[1] {
|
||||
// when shift_reg[1] is HdlNone that means shift_reg would be completely empty in the next clock
|
||||
// cycle, so instead fill it with the next byte if there is one.
|
||||
#[hdl]
|
||||
if let HdlSome(byte) = input_buf_reg {
|
||||
connect(input_buf_reg, HdlNone());
|
||||
connect(shift_reg, [HdlSome(true); _]);
|
||||
connect(shift_reg[0], HdlSome(false));
|
||||
for (i, v) in (*shift_reg)[1..][..8].iter().enumerate() {
|
||||
connect(v, HdlSome(byte[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
if let HdlSome(_) = input_buf_reg {
|
||||
connect(input_byte.ready, false);
|
||||
} else {
|
||||
connect(input_byte.ready, true);
|
||||
connect(input_buf_reg, input_byte.data);
|
||||
}
|
||||
}
|
||||
|
||||
/// `rx_synchronized` is [`peripherals::Uart::rx`], but after being synchronized to `cd.clk`.
|
||||
/// `output_byte` is `HdlSome` for 1 clock cycle for every received byte.
|
||||
#[hdl_module]
|
||||
pub fn receiver_no_queue() {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let tick: Bool = m.input();
|
||||
#[hdl]
|
||||
let rx_synchronized: Bool = m.input();
|
||||
#[hdl]
|
||||
let output_byte: HdlOption<UInt<8>> = m.output();
|
||||
|
||||
const BITS_PER_BYTE: usize = 8;
|
||||
|
||||
#[hdl]
|
||||
let output_byte_reg: HdlOption<UInt<{ BITS_PER_BYTE }>> =
|
||||
reg_builder().clock_domain(cd).reset(HdlNone());
|
||||
|
||||
connect(output_byte, output_byte_reg);
|
||||
connect(output_byte_reg, HdlNone()); // by default always reset to HdlNone()
|
||||
|
||||
#[hdl]
|
||||
let tick_count_reg = reg_builder()
|
||||
.clock_domain(cd)
|
||||
.reset(0u8.cast_to(UInt::range(0..TICKS_PER_BAUD)));
|
||||
|
||||
#[hdl]
|
||||
let shift_reg: UInt<{ BITS_PER_BYTE }> = reg_builder().clock_domain(cd).reset(0u8);
|
||||
|
||||
#[hdl]
|
||||
enum State {
|
||||
WaitingForStartBit,
|
||||
StartBit,
|
||||
DataBits(UIntInRange<0, { BITS_PER_BYTE }>),
|
||||
StopBit,
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
let state_reg = reg_builder()
|
||||
.clock_domain(cd)
|
||||
.reset(State.WaitingForStartBit());
|
||||
|
||||
#[hdl]
|
||||
if tick {
|
||||
#[hdl]
|
||||
match state_reg {
|
||||
State::WaitingForStartBit => {
|
||||
connect(tick_count_reg, tick_count_reg.ty().zero());
|
||||
#[hdl]
|
||||
if !rx_synchronized {
|
||||
connect(state_reg, State.StartBit());
|
||||
}
|
||||
}
|
||||
State::StartBit =>
|
||||
{
|
||||
#[hdl]
|
||||
if tick_count_reg.cmp_eq(TICKS_PER_BAUD - 1) {
|
||||
connect(tick_count_reg, tick_count_reg.ty().zero());
|
||||
connect(state_reg, State.DataBits(0u8.cast_to(State.DataBits)));
|
||||
} else {
|
||||
connect_any(tick_count_reg, tick_count_reg + 1u8);
|
||||
}
|
||||
}
|
||||
State::DataBits(count) => {
|
||||
#[hdl]
|
||||
if tick_count_reg.cmp_eq(TICKS_PER_BAUD - 1) {
|
||||
connect(tick_count_reg, tick_count_reg.ty().zero());
|
||||
#[hdl]
|
||||
if count.cmp_eq(BITS_PER_BYTE - 1) {
|
||||
connect(state_reg, State.StopBit());
|
||||
connect(output_byte_reg, HdlSome(shift_reg));
|
||||
} else {
|
||||
connect(
|
||||
state_reg,
|
||||
State.DataBits((count.cast_to(UInt[8]) + 1u8).cast_to(State.DataBits)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
connect_any(tick_count_reg, tick_count_reg + 1u8);
|
||||
}
|
||||
#[hdl]
|
||||
if tick_count_reg.cmp_eq(TICKS_PER_BAUD / 2) {
|
||||
connect_any(
|
||||
shift_reg,
|
||||
(shift_reg >> 1) | (rx_synchronized.cast_to_static::<UInt<1>>() << 7),
|
||||
);
|
||||
}
|
||||
}
|
||||
State::StopBit => {
|
||||
#[hdl]
|
||||
if tick_count_reg.cmp_eq(TICKS_PER_BAUD / 2) {
|
||||
// go to WaitingForStartBit in the middle of the stop bit so we can adjust if the sender is faster than us.
|
||||
connect(tick_count_reg, tick_count_reg.ty().zero());
|
||||
connect(state_reg, State.WaitingForStartBit());
|
||||
} else {
|
||||
connect_any(tick_count_reg, tick_count_reg + 1u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
pub enum ReceiverQueueStatus {
|
||||
QueueEmpty,
|
||||
QueueNotEmpty,
|
||||
QueueAlmostFull,
|
||||
QueueFull,
|
||||
QueueOverflowed,
|
||||
}
|
||||
|
||||
impl ReceiverQueueStatus {
|
||||
pub fn sim_as_u8(this: impl ToSimValue<Type = Self>) -> u8 {
|
||||
SimValue::bits(&this.into_sim_value())
|
||||
.cast_to_static::<UInt<8>>()
|
||||
.as_int()
|
||||
}
|
||||
#[hdl]
|
||||
pub fn for_queue_len_sim(current_count: usize, queue_size: NonZeroUsize) -> SimValue<Self> {
|
||||
if current_count == 0 {
|
||||
#[hdl(sim)]
|
||||
ReceiverQueueStatus.QueueEmpty()
|
||||
} else if current_count == queue_size.get() {
|
||||
#[hdl(sim)]
|
||||
ReceiverQueueStatus.QueueFull()
|
||||
} else if current_count == queue_size.get() - 1 {
|
||||
#[hdl(sim)]
|
||||
ReceiverQueueStatus.QueueAlmostFull()
|
||||
} else if current_count > queue_size.get() {
|
||||
#[hdl(sim)]
|
||||
ReceiverQueueStatus.QueueOverflowed()
|
||||
} else {
|
||||
#[hdl(sim)]
|
||||
ReceiverQueueStatus.QueueNotEmpty()
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
pub fn for_queue_len_as_u8(current_count: usize, queue_size: NonZeroUsize) -> u8 {
|
||||
Self::sim_as_u8(Self::for_queue_len_sim(current_count, queue_size))
|
||||
}
|
||||
}
|
||||
|
||||
/// `rx_synchronized` is [`peripherals::Uart::rx`], but after being synchronized to `cd.clk`.
|
||||
#[hdl_module]
|
||||
pub fn receiver(queue_capacity: NonZeroUsize) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let tick: Bool = m.input();
|
||||
#[hdl]
|
||||
let rx_synchronized: Bool = m.input();
|
||||
#[hdl]
|
||||
let output_byte: ReadyValid<UInt<8>> = m.output();
|
||||
#[hdl]
|
||||
let queue_status: ReceiverQueueStatus = m.output();
|
||||
|
||||
#[hdl]
|
||||
let queue = instance(queue(UInt::<8>::new_static(), queue_capacity, false, false));
|
||||
|
||||
connect(queue.cd, cd);
|
||||
connect(output_byte, queue.out);
|
||||
|
||||
#[hdl]
|
||||
let queue_overflowed_reg = reg_builder().clock_domain(cd).reset(false);
|
||||
|
||||
#[hdl]
|
||||
if queue_overflowed_reg {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueOverflowed());
|
||||
} else if queue.count.cmp_eq(0u8) {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueEmpty());
|
||||
} else if queue.count.cmp_eq(queue_capacity) {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueFull());
|
||||
} else if queue.count.cmp_eq(queue_capacity.get() - 1) {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueAlmostFull());
|
||||
} else {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueNotEmpty());
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
if ReadyValid::firing(output_byte) {
|
||||
// clear overflow when a byte is read from the queue
|
||||
connect(queue_overflowed_reg, false);
|
||||
}
|
||||
|
||||
// we ignore queue.inp.ready other than noting an overflow when there was data but the queue wasn't ready
|
||||
#[hdl]
|
||||
if !queue.inp.ready {
|
||||
#[hdl]
|
||||
if let HdlSome(_) = queue.inp.data {
|
||||
connect(queue_overflowed_reg, true);
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
let inner = instance(receiver_no_queue());
|
||||
connect(inner.cd, cd);
|
||||
connect(inner.tick, tick);
|
||||
connect(inner.rx_synchronized, rx_synchronized);
|
||||
connect(queue.inp.data, inner.output_byte);
|
||||
}
|
||||
|
||||
pub const SIMPLE_UART_RECEIVE_OFFSET: u64 = 0;
|
||||
pub const SIMPLE_UART_TRANSMIT_OFFSET: u64 = 0;
|
||||
pub const SIMPLE_UART_STATUS_OFFSET: u64 = 1;
|
||||
pub const SIMPLE_UART_LOG2_BUS_WIDTH: u8 = 1;
|
||||
pub const SIMPLE_UART_USED_SIZE: NonZeroU64 = NonZeroU64::new(2).expect("known non-zero");
|
||||
pub const SIMPLE_UART_ADDRESS_SIZE: NonZeroU64 = NonZeroU64::new(1 << 6).expect("known non-zero");
|
||||
|
||||
#[hdl(no_static)]
|
||||
struct Operation<C: PhantomConstGet<MemoryInterfaceConfig>> {
|
||||
start: MemoryOperationStart<C>,
|
||||
finish: HdlOption<MemoryOperationFinish<C>>,
|
||||
}
|
||||
|
||||
pub const fn simple_uart_memory_interface_config(
|
||||
op_id_width: usize,
|
||||
start_address: Wrapping<u64>,
|
||||
) -> MemoryInterfaceConfig {
|
||||
assert!(
|
||||
start_address
|
||||
.0
|
||||
.is_multiple_of(SIMPLE_UART_ADDRESS_SIZE.get()),
|
||||
"start_address must be properly aligned"
|
||||
);
|
||||
MemoryInterfaceConfig {
|
||||
log2_bus_width_in_bytes: SIMPLE_UART_LOG2_BUS_WIDTH,
|
||||
queue_capacity: const { NonZeroUsize::new(1).unwrap() },
|
||||
op_id_width,
|
||||
address_range: AddressRange::Limited {
|
||||
start: start_address,
|
||||
size: SIMPLE_UART_ADDRESS_SIZE,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
pub fn simple_uart(
|
||||
config: PhantomConst<MemoryInterfaceConfig>,
|
||||
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
|
||||
baud_rate: f64,
|
||||
receiver_queue_size: NonZeroUsize,
|
||||
) {
|
||||
let start_address = config.get().address_range.start();
|
||||
assert_eq!(
|
||||
*config.get(),
|
||||
simple_uart_memory_interface_config(config.get().op_id_width, start_address),
|
||||
);
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
|
||||
m.input(MemoryInterface[config]);
|
||||
#[hdl]
|
||||
let uart: peripherals::Uart = m.output();
|
||||
|
||||
#[hdl]
|
||||
let rx_sync_intermediate_reg: Bool = reg_builder().clock_domain(cd).reset(true);
|
||||
annotate(rx_sync_intermediate_reg, DontTouchAnnotation);
|
||||
#[hdl]
|
||||
let rx_sync_final_reg: Bool = reg_builder().clock_domain(cd).reset(true);
|
||||
annotate(rx_sync_final_reg, DontTouchAnnotation);
|
||||
connect(rx_sync_intermediate_reg, uart.rx);
|
||||
connect(rx_sync_final_reg, rx_sync_intermediate_reg);
|
||||
#[hdl]
|
||||
let rx_synchronized: Bool = wire();
|
||||
annotate(rx_synchronized, DontTouchAnnotation);
|
||||
connect(rx_synchronized, rx_sync_final_reg);
|
||||
|
||||
#[hdl]
|
||||
let clk_gen = instance(uart_clock_gen(clock_input_properties, baud_rate));
|
||||
connect(clk_gen.cd, cd);
|
||||
|
||||
#[hdl]
|
||||
let transmitter = instance(transmitter());
|
||||
connect(transmitter.cd, cd);
|
||||
connect(transmitter.tick, clk_gen.tick);
|
||||
connect(uart.tx, transmitter.tx);
|
||||
|
||||
#[hdl]
|
||||
let receiver = instance(receiver(receiver_queue_size));
|
||||
connect(receiver.cd, cd);
|
||||
connect(receiver.tick, clk_gen.tick);
|
||||
connect(receiver.rx_synchronized, rx_synchronized);
|
||||
|
||||
#[hdl]
|
||||
let operation_reg = reg_builder()
|
||||
.clock_domain(cd)
|
||||
.reset(HdlOption[Operation[config]].HdlNone());
|
||||
|
||||
let next_op_ids_ty = memory_interface.ty().next_op_ids.HdlSome;
|
||||
#[hdl]
|
||||
if let HdlSome(operation) = operation_reg {
|
||||
#[hdl]
|
||||
let MemoryInterface::<_> {
|
||||
start,
|
||||
finish,
|
||||
next_op_ids,
|
||||
config: _,
|
||||
} = memory_interface;
|
||||
connect(start.ready, false);
|
||||
connect(finish.data, operation.finish);
|
||||
connect(
|
||||
next_op_ids,
|
||||
HdlSome(ArrayVec::from_parts_unchecked(
|
||||
repeat(operation.start.op_id, next_op_ids_ty.capacity()),
|
||||
1u8.cast_to(next_op_ids_ty.len_ty()),
|
||||
)),
|
||||
);
|
||||
connect(transmitter.input_byte.data, HdlNone());
|
||||
connect(receiver.output_byte.ready, false);
|
||||
#[hdl]
|
||||
if let HdlSome(_) = operation.finish {
|
||||
#[hdl]
|
||||
if finish.ready {
|
||||
connect(operation_reg, operation_reg.ty().HdlNone());
|
||||
}
|
||||
} else {
|
||||
#[hdl]
|
||||
let MemoryOperationStart::<_> {
|
||||
kind,
|
||||
addr,
|
||||
write_data,
|
||||
rw_mask,
|
||||
op_id: _,
|
||||
config: _,
|
||||
} = operation.start;
|
||||
#[hdl]
|
||||
let valid_addr = wire();
|
||||
connect(valid_addr, true);
|
||||
#[hdl]
|
||||
let all_ready = wire();
|
||||
connect(all_ready, true);
|
||||
#[hdl]
|
||||
let read_data = wire(write_data.ty());
|
||||
for (byte_index, ((rw_mask, write_data), read_data)) in rw_mask
|
||||
.into_iter()
|
||||
.zip(write_data)
|
||||
.zip(read_data)
|
||||
.enumerate()
|
||||
{
|
||||
let byte_addr = (addr | byte_index).cast_to_static::<UInt<64>>();
|
||||
connect(read_data, 0u8);
|
||||
#[hdl]
|
||||
if rw_mask {
|
||||
#[hdl]
|
||||
match kind {
|
||||
MemoryOperationKind::Read => {
|
||||
#[hdl]
|
||||
if byte_addr
|
||||
.cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_RECEIVE_OFFSET))
|
||||
{
|
||||
connect(receiver.output_byte.ready, valid_addr);
|
||||
#[hdl]
|
||||
if let HdlSome(byte) = receiver.output_byte.data {
|
||||
connect(read_data, byte);
|
||||
}
|
||||
// if there is no byte ready yet, we read a zero to avoid blocking the CPU on external inputs.
|
||||
} else if byte_addr
|
||||
.cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_STATUS_OFFSET))
|
||||
{
|
||||
connect(
|
||||
read_data,
|
||||
receiver
|
||||
.queue_status
|
||||
.cast_to_bits()
|
||||
.cast_to_static::<UInt<8>>(),
|
||||
);
|
||||
} else {
|
||||
connect(valid_addr, false);
|
||||
}
|
||||
}
|
||||
MemoryOperationKind::Write => {
|
||||
#[hdl]
|
||||
if byte_addr
|
||||
.cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_TRANSMIT_OFFSET))
|
||||
{
|
||||
#[hdl]
|
||||
if !transmitter.input_byte.ready {
|
||||
connect(all_ready, false);
|
||||
}
|
||||
#[hdl]
|
||||
if valid_addr {
|
||||
connect(transmitter.input_byte.data, HdlSome(write_data));
|
||||
}
|
||||
} else {
|
||||
connect(valid_addr, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
if !valid_addr {
|
||||
connect(
|
||||
operation_reg,
|
||||
HdlSome(
|
||||
#[hdl]
|
||||
Operation::<_> {
|
||||
start: operation.start,
|
||||
finish: HdlSome(
|
||||
#[hdl]
|
||||
MemoryOperationFinish::<_> {
|
||||
kind: MemoryOperationFinishKind
|
||||
.Error(MemoryOperationErrorKind.Generic()),
|
||||
read_data,
|
||||
config,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
} else if all_ready {
|
||||
connect(
|
||||
operation_reg,
|
||||
HdlSome(
|
||||
#[hdl]
|
||||
Operation::<_> {
|
||||
start: operation.start,
|
||||
finish: HdlSome(
|
||||
#[hdl]
|
||||
MemoryOperationFinish::<_> {
|
||||
kind: MemoryOperationFinishKind.Success(kind),
|
||||
read_data,
|
||||
config,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#[hdl]
|
||||
let MemoryInterface::<_> {
|
||||
start,
|
||||
finish,
|
||||
next_op_ids,
|
||||
config: _,
|
||||
} = memory_interface;
|
||||
connect(start.ready, true);
|
||||
connect(finish.data, finish.ty().data.HdlNone());
|
||||
connect(
|
||||
next_op_ids,
|
||||
HdlSome(next_op_ids_ty.new_sim(next_op_ids_ty.element().zero())),
|
||||
);
|
||||
#[hdl]
|
||||
if let HdlSome(start) = start.data {
|
||||
connect(
|
||||
operation_reg,
|
||||
HdlSome(
|
||||
#[hdl]
|
||||
Operation::<_> {
|
||||
start,
|
||||
finish: operation_reg.ty().HdlSome.finish.HdlNone(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
connect(transmitter.input_byte.data, HdlNone());
|
||||
connect(receiver.output_byte.ready, false);
|
||||
}
|
||||
}
|
||||
192
crates/cpu/src/rename_execute_retire.rs
Normal file
192
crates/cpu/src/rename_execute_retire.rs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
config::{CpuConfig, CpuConfigFetchWidth, CpuConfigRobSize, PhantomConstCpuConfig},
|
||||
instruction::{MOp, MOpRegNum, PRegNum},
|
||||
next_pc::{RetireToNextPcInterfaceInner, SimValueDefault},
|
||||
util::array_vec::ArrayVec,
|
||||
};
|
||||
use fayalite::{
|
||||
int::UIntInRangeInclusiveType, prelude::*, ty::OpaqueSimValue, util::ready_valid::ReadyValid,
|
||||
};
|
||||
|
||||
#[hdl]
|
||||
/// A µOp along with the state needed for this instance of the µOp.
|
||||
pub struct MOpInstance<MOp> {
|
||||
pub fetch_block_id: UInt<8>,
|
||||
pub id: UInt<12>,
|
||||
pub pc: UInt<64>,
|
||||
/// initialized to 0 by decoder, overwritten by `next_pc()`
|
||||
pub predicted_next_pc: UInt<64>,
|
||||
pub size_in_bytes: UInt<4>,
|
||||
/// `true` if this µOp is the first µOp in the ISA-level instruction.
|
||||
/// In general, a single µOp can't be cancelled by itself,
|
||||
/// it needs to be cancelled along with all other µOps that
|
||||
/// come from the same ISA-level instruction.
|
||||
pub is_first_mop_in_insn: Bool,
|
||||
pub mop: MOp,
|
||||
}
|
||||
|
||||
#[hdl(no_static)]
|
||||
/// TODO: merge with [`crate::next_pc::PostDecodeOutputInterface`]
|
||||
pub struct PostDecodeOutputInterface<C: PhantomConstGet<CpuConfig>> {
|
||||
pub insns: ArrayVec<MOpInstance<MOp>, CpuConfigFetchWidth<C>>,
|
||||
#[hdl(flip)]
|
||||
pub ready: UIntInRangeInclusiveType<ConstUsize<0>, CpuConfigFetchWidth<C>>,
|
||||
/// tells the rename/execute/retire circuit to cancel all non-retired instructions
|
||||
pub cancel: ReadyValid<()>,
|
||||
pub config: C,
|
||||
}
|
||||
|
||||
#[hdl(no_static)]
|
||||
/// handles updating speculative branch predictor state (e.g. branch histories)
|
||||
/// when instructions retire, as well as updating state when a
|
||||
/// branch instruction is mis-speculated.
|
||||
pub struct RetireToNextPcInterface<C: PhantomConstGet<CpuConfig>> {
|
||||
pub inner: ReadyValid<RetireToNextPcInterfaceInner<C>>,
|
||||
/// only for debugging
|
||||
pub next_insns: HdlOption<ArrayVec<MOpInstance<MOp>, CpuConfigRobSize<C>>>,
|
||||
}
|
||||
|
||||
#[hdl(no_static)]
|
||||
pub struct RenameExecuteRetireDebugState<C: PhantomConstGet<CpuConfig>> {
|
||||
rename_table: Array<PRegNum<C>, { 1 << MOpRegNum::WIDTH }>,
|
||||
retire_rename_table: Array<PRegNum<C>, { 1 << MOpRegNum::WIDTH }>,
|
||||
rob: ArrayVec<RobEntryDebugState<C>, CpuConfigRobSize<C>>,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> SimValueDefault for RenameExecuteRetireDebugState<C> {
|
||||
fn sim_value_default(self) -> SimValue<Self> {
|
||||
SimValue::from_opaque(
|
||||
self,
|
||||
OpaqueSimValue::from_bits(UInt::new(self.canonical().bit_width()).zero()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct RenameTable<C: PhantomConstCpuConfig> {
|
||||
entries: Box<[SimValue<PRegNum<C>>; 1 << MOpRegNum::WIDTH]>,
|
||||
config: C,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> RenameTable<C> {
|
||||
fn new(config: C) -> Self {
|
||||
Self {
|
||||
entries: vec![PRegNum[config].const_zero().into_sim_value(); 1 << MOpRegNum::WIDTH]
|
||||
.try_into()
|
||||
.expect("size is known to match"),
|
||||
config,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl(no_static)]
|
||||
struct RobEntryDebugState<C: PhantomConstGet<CpuConfig>> {
|
||||
config: C,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RobEntry<C: PhantomConstCpuConfig> {
|
||||
config: C,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RenameExecuteRetireState<C: PhantomConstCpuConfig> {
|
||||
rename_table: RenameTable<C>,
|
||||
retire_rename_table: RenameTable<C>,
|
||||
rob: VecDeque<RobEntry<C>>,
|
||||
}
|
||||
|
||||
impl<C: PhantomConstCpuConfig> RenameExecuteRetireState<C> {
|
||||
fn new(config: C) -> Self {
|
||||
let rename_table = RenameTable::new(config);
|
||||
Self {
|
||||
rename_table: rename_table.clone(),
|
||||
retire_rename_table: rename_table,
|
||||
rob: VecDeque::with_capacity(CpuConfigRobSize[config]),
|
||||
}
|
||||
}
|
||||
async fn write_for_debug(
|
||||
&self,
|
||||
sim: &mut ExternModuleSimulationState,
|
||||
state_for_debug: Expr<RenameExecuteRetireDebugState<C>>,
|
||||
) {
|
||||
// TODO
|
||||
}
|
||||
async fn write_to_next_pc_next_insns(
|
||||
&self,
|
||||
sim: &mut ExternModuleSimulationState,
|
||||
next_insns: Expr<HdlOption<ArrayVec<MOpInstance<MOp>, CpuConfigRobSize<C>>>>,
|
||||
) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
async fn rename_execute_retire_run(
|
||||
mut sim: ExternModuleSimulationState,
|
||||
cd: Expr<ClockDomain>,
|
||||
from_post_decode: Expr<PostDecodeOutputInterface<PhantomConst<CpuConfig>>>,
|
||||
to_next_pc: Expr<RetireToNextPcInterface<PhantomConst<CpuConfig>>>,
|
||||
state_for_debug: Expr<RenameExecuteRetireDebugState<PhantomConst<CpuConfig>>>,
|
||||
config: PhantomConst<CpuConfig>,
|
||||
) {
|
||||
let mut state = RenameExecuteRetireState::new(config);
|
||||
loop {
|
||||
state
|
||||
.write_to_next_pc_next_insns(&mut sim, to_next_pc.next_insns)
|
||||
.await;
|
||||
state.write_for_debug(&mut sim, state_for_debug).await;
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
todo!("step state based on I/O");
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
pub fn rename_execute_retire(config: PhantomConst<CpuConfig>) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let from_post_decode: PostDecodeOutputInterface<PhantomConst<CpuConfig>> =
|
||||
m.input(PostDecodeOutputInterface[config]);
|
||||
#[hdl]
|
||||
let to_next_pc: RetireToNextPcInterface<PhantomConst<CpuConfig>> =
|
||||
m.output(RetireToNextPcInterface[config]);
|
||||
#[hdl]
|
||||
let state_for_debug: RenameExecuteRetireDebugState<PhantomConst<CpuConfig>> =
|
||||
m.output(RenameExecuteRetireDebugState[config]);
|
||||
m.register_clock_for_past(cd.clk);
|
||||
m.extern_module_simulation_fn(
|
||||
(cd, from_post_decode, to_next_pc, state_for_debug, config),
|
||||
|(cd, from_post_decode, to_next_pc, state_for_debug, config), mut sim| async move {
|
||||
sim.write(state_for_debug, state_for_debug.ty().sim_value_default())
|
||||
.await;
|
||||
sim.resettable(
|
||||
cd,
|
||||
|mut sim: ExternModuleSimulationState| async move {
|
||||
sim.write(from_post_decode.ready, 0usize).await;
|
||||
sim.write(from_post_decode.cancel.ready, false).await;
|
||||
sim.write(to_next_pc.inner.data, to_next_pc.ty().inner.data.HdlNone())
|
||||
.await;
|
||||
sim.write(to_next_pc.next_insns, to_next_pc.ty().next_insns.HdlNone())
|
||||
.await;
|
||||
},
|
||||
|sim, ()| {
|
||||
rename_execute_retire_run(
|
||||
sim,
|
||||
cd,
|
||||
from_post_decode,
|
||||
to_next_pc,
|
||||
state_for_debug,
|
||||
config,
|
||||
)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
// See Notices.txt for copyright information
|
||||
|
||||
use crate::{
|
||||
config::CpuConfig,
|
||||
config::{CpuConfig, PhantomConstCpuConfig},
|
||||
instruction::{
|
||||
AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait,
|
||||
MOpVariantVisitOps, MOpVariantVisitor, MOpVisitVariants, RenamedMOp, UnitOutRegNum,
|
||||
|
|
@ -48,7 +48,7 @@ macro_rules! all_units {
|
|||
}
|
||||
|
||||
impl $UnitKind {
|
||||
pub fn unit(self, config: &CpuConfig, unit_index: usize) -> DynUnit {
|
||||
pub fn unit(self, config: PhantomConst<CpuConfig>, unit_index: usize) -> DynUnit {
|
||||
match self {
|
||||
$($UnitKind::$Unit => $create_dyn_unit_fn(config, unit_index),)*
|
||||
}
|
||||
|
|
@ -277,9 +277,9 @@ pub struct UnitResultCompleted<ExtraOut> {
|
|||
pub extra_out: ExtraOut,
|
||||
}
|
||||
|
||||
#[hdl(cmp_eq)]
|
||||
pub struct UnitOutputWrite<OutRegNumWidth: Size> {
|
||||
pub which: UnitOutRegNum<OutRegNumWidth>,
|
||||
#[hdl(cmp_eq, no_static)]
|
||||
pub struct UnitOutputWrite<C: PhantomConstGet<CpuConfig>> {
|
||||
pub which: UnitOutRegNum<C>,
|
||||
pub value: PRegValue,
|
||||
}
|
||||
|
||||
|
|
@ -300,21 +300,21 @@ impl<ExtraOut: Type> UnitResult<ExtraOut> {
|
|||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
pub struct UnitOutput<OutRegNumWidth: Size, ExtraOut> {
|
||||
pub which: UnitOutRegNum<OutRegNumWidth>,
|
||||
#[hdl(no_static)]
|
||||
pub struct UnitOutput<C: PhantomConstGet<CpuConfig>, ExtraOut> {
|
||||
pub which: UnitOutRegNum<C>,
|
||||
pub result: UnitResult<ExtraOut>,
|
||||
}
|
||||
|
||||
impl<OutRegNumWidth: Size, ExtraOut: Type> UnitOutput<OutRegNumWidth, ExtraOut> {
|
||||
impl<C: PhantomConstCpuConfig, ExtraOut: Type> UnitOutput<C, ExtraOut> {
|
||||
pub fn extra_out_ty(self) -> ExtraOut {
|
||||
self.result.extra_out_ty()
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl(cmp_eq)]
|
||||
pub struct UnitCancelInput<OutRegNumWidth: Size> {
|
||||
pub which: UnitOutRegNum<OutRegNumWidth>,
|
||||
#[hdl(cmp_eq, no_static)]
|
||||
pub struct UnitCancelInput<C: PhantomConstGet<CpuConfig>> {
|
||||
pub which: UnitOutRegNum<C>,
|
||||
}
|
||||
|
||||
pub trait UnitTrait:
|
||||
|
|
@ -332,7 +332,7 @@ pub trait UnitTrait:
|
|||
|
||||
fn extract_mop(
|
||||
&self,
|
||||
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
|
||||
mop: Expr<RenamedMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize>>,
|
||||
) -> Expr<HdlOption<Self::MOp>>;
|
||||
|
||||
fn module(&self) -> Interned<Module<Self::Type>>;
|
||||
|
|
@ -340,7 +340,7 @@ pub trait UnitTrait:
|
|||
fn unit_to_reg_alloc(
|
||||
&self,
|
||||
this: Expr<Self::Type>,
|
||||
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>>;
|
||||
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>>;
|
||||
|
||||
fn cd(&self, this: Expr<Self::Type>) -> Expr<ClockDomain>;
|
||||
|
||||
|
|
@ -390,7 +390,7 @@ impl UnitTrait for DynUnit {
|
|||
|
||||
fn extract_mop(
|
||||
&self,
|
||||
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
|
||||
mop: Expr<RenamedMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize>>,
|
||||
) -> Expr<HdlOption<Self::MOp>> {
|
||||
self.unit.extract_mop(mop)
|
||||
}
|
||||
|
|
@ -402,7 +402,7 @@ impl UnitTrait for DynUnit {
|
|||
fn unit_to_reg_alloc(
|
||||
&self,
|
||||
this: Expr<Self::Type>,
|
||||
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>> {
|
||||
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
|
||||
self.unit.unit_to_reg_alloc(this)
|
||||
}
|
||||
|
||||
|
|
@ -445,7 +445,7 @@ impl<T: UnitTrait + Clone + std::hash::Hash + Eq> UnitTrait for DynUnitWrapper<T
|
|||
|
||||
fn extract_mop(
|
||||
&self,
|
||||
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
|
||||
mop: Expr<RenamedMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize>>,
|
||||
) -> Expr<HdlOption<Self::MOp>> {
|
||||
Expr::from_enum(Expr::as_enum(self.0.extract_mop(mop)))
|
||||
}
|
||||
|
|
@ -457,7 +457,7 @@ impl<T: UnitTrait + Clone + std::hash::Hash + Eq> UnitTrait for DynUnitWrapper<T
|
|||
fn unit_to_reg_alloc(
|
||||
&self,
|
||||
this: Expr<Self::Type>,
|
||||
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>> {
|
||||
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
|
||||
Expr::from_bundle(Expr::as_bundle(
|
||||
self.0.unit_to_reg_alloc(Expr::from_bundle(this)),
|
||||
))
|
||||
|
|
|
|||
|
|
@ -19,16 +19,13 @@ use crate::{
|
|||
},
|
||||
};
|
||||
use fayalite::{
|
||||
intern::{Intern, Interned},
|
||||
module::wire_with_loc,
|
||||
prelude::*,
|
||||
util::ready_valid::ReadyValid,
|
||||
intern::Interned, module::wire_with_loc, prelude::*, util::ready_valid::ReadyValid,
|
||||
};
|
||||
use std::{collections::HashMap, ops::RangeTo};
|
||||
|
||||
#[hdl]
|
||||
fn add_sub<SrcCount: KnownSize>(
|
||||
mop: Expr<AddSubMOp<UnitOutRegNum<DynSize>, DynSize, SrcCount>>,
|
||||
mop: Expr<AddSubMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize, SrcCount>>,
|
||||
pc: Expr<UInt<64>>,
|
||||
flags_mode: Expr<FlagsMode>,
|
||||
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||
|
|
@ -245,7 +242,7 @@ fn add_sub<SrcCount: KnownSize>(
|
|||
|
||||
#[hdl]
|
||||
fn logical_flags(
|
||||
mop: Expr<LogicalFlagsMOp<UnitOutRegNum<DynSize>, DynSize>>,
|
||||
mop: Expr<LogicalFlagsMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize>>,
|
||||
flags_mode: Expr<FlagsMode>,
|
||||
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||
) -> Expr<UnitResultCompleted<()>> {
|
||||
|
|
@ -259,7 +256,7 @@ fn logical_flags(
|
|||
|
||||
#[hdl]
|
||||
fn logical(
|
||||
mop: Expr<LogicalMOp<UnitOutRegNum<DynSize>, DynSize, ConstUsize<2>>>,
|
||||
mop: Expr<LogicalMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize, ConstUsize<2>>>,
|
||||
flags_mode: Expr<FlagsMode>,
|
||||
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||
) -> Expr<UnitResultCompleted<()>> {
|
||||
|
|
@ -273,7 +270,7 @@ fn logical(
|
|||
|
||||
#[hdl]
|
||||
fn logical_i(
|
||||
mop: Expr<LogicalMOp<UnitOutRegNum<DynSize>, DynSize, ConstUsize<1>>>,
|
||||
mop: Expr<LogicalMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize, ConstUsize<1>>>,
|
||||
flags_mode: Expr<FlagsMode>,
|
||||
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||
) -> Expr<UnitResultCompleted<()>> {
|
||||
|
|
@ -287,7 +284,7 @@ fn logical_i(
|
|||
|
||||
#[hdl]
|
||||
fn shift_rotate(
|
||||
mop: Expr<ShiftRotateMOp<UnitOutRegNum<DynSize>, DynSize>>,
|
||||
mop: Expr<ShiftRotateMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize>>,
|
||||
flags_mode: Expr<FlagsMode>,
|
||||
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||
) -> Expr<UnitResultCompleted<()>> {
|
||||
|
|
@ -301,7 +298,7 @@ fn shift_rotate(
|
|||
|
||||
#[hdl]
|
||||
fn compare<SrcCount: KnownSize>(
|
||||
mop: Expr<CompareMOp<UnitOutRegNum<DynSize>, DynSize, SrcCount>>,
|
||||
mop: Expr<CompareMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize, SrcCount>>,
|
||||
flags_mode: Expr<FlagsMode>,
|
||||
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||
) -> Expr<UnitResultCompleted<()>> {
|
||||
|
|
@ -315,7 +312,7 @@ fn compare<SrcCount: KnownSize>(
|
|||
|
||||
#[hdl]
|
||||
fn branch<SrcCount: KnownSize>(
|
||||
mop: Expr<BranchMOp<UnitOutRegNum<DynSize>, DynSize, SrcCount>>,
|
||||
mop: Expr<BranchMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize, SrcCount>>,
|
||||
pc: Expr<UInt<64>>,
|
||||
flags_mode: Expr<FlagsMode>,
|
||||
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||
|
|
@ -330,7 +327,7 @@ fn branch<SrcCount: KnownSize>(
|
|||
|
||||
#[hdl]
|
||||
fn read_special(
|
||||
mop: Expr<ReadSpecialMOp<UnitOutRegNum<DynSize>, DynSize>>,
|
||||
mop: Expr<ReadSpecialMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize>>,
|
||||
pc: Expr<UInt<64>>,
|
||||
flags_mode: Expr<FlagsMode>,
|
||||
src_values: Expr<Array<PRegValue, { COMMON_MOP_SRC_LEN }>>,
|
||||
|
|
@ -344,20 +341,18 @@ fn read_special(
|
|||
}
|
||||
|
||||
#[hdl_module]
|
||||
pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
|
||||
pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let unit_to_reg_alloc: UnitToRegAlloc<
|
||||
AluBranchMOp<UnitOutRegNum<DynSize>, DynSize>,
|
||||
PhantomConst<CpuConfig>,
|
||||
AluBranchMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize>,
|
||||
(),
|
||||
DynSize,
|
||||
DynSize,
|
||||
DynSize,
|
||||
> = m.output(config.unit_to_reg_alloc(
|
||||
AluBranchMOp[config.unit_out_reg_num()][config.p_reg_num_width()],
|
||||
(),
|
||||
));
|
||||
> = m.output(
|
||||
UnitToRegAlloc[config][AluBranchMOp[UnitOutRegNum[config]][config.get().p_reg_num_width()]]
|
||||
[()],
|
||||
);
|
||||
#[hdl]
|
||||
let global_state: GlobalState = m.input();
|
||||
|
||||
|
|
@ -375,10 +370,11 @@ pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
|
|||
#[hdl]
|
||||
if let HdlSome(execute_start) = ReadyValid::firing_data(unit_base.execute_start) {
|
||||
#[hdl]
|
||||
let ExecuteStart::<_> {
|
||||
let ExecuteStart::<_, _> {
|
||||
mop,
|
||||
pc,
|
||||
src_values,
|
||||
config: _,
|
||||
} = execute_start;
|
||||
#[hdl]
|
||||
match mop {
|
||||
|
|
@ -580,14 +576,14 @@ pub fn alu_branch(config: &CpuConfig, unit_index: usize) {
|
|||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct AluBranch {
|
||||
config: Interned<CpuConfig>,
|
||||
config: PhantomConst<CpuConfig>,
|
||||
module: Interned<Module<alu_branch>>,
|
||||
}
|
||||
|
||||
impl AluBranch {
|
||||
pub fn new(config: &CpuConfig, unit_index: usize) -> Self {
|
||||
pub fn new(config: PhantomConst<CpuConfig>, unit_index: usize) -> Self {
|
||||
Self {
|
||||
config: config.intern(),
|
||||
config,
|
||||
module: alu_branch(config, unit_index),
|
||||
}
|
||||
}
|
||||
|
|
@ -596,7 +592,7 @@ impl AluBranch {
|
|||
impl UnitTrait for AluBranch {
|
||||
type Type = alu_branch;
|
||||
type ExtraOut = ();
|
||||
type MOp = AluBranchMOp<UnitOutRegNum<DynSize>, DynSize>;
|
||||
type MOp = AluBranchMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize>;
|
||||
|
||||
fn ty(&self) -> Self::Type {
|
||||
self.module.io_ty()
|
||||
|
|
@ -616,7 +612,7 @@ impl UnitTrait for AluBranch {
|
|||
|
||||
fn extract_mop(
|
||||
&self,
|
||||
mop: Expr<RenamedMOp<UnitOutRegNum<DynSize>, DynSize>>,
|
||||
mop: Expr<RenamedMOp<UnitOutRegNum<PhantomConst<CpuConfig>>, DynSize>>,
|
||||
) -> Expr<HdlOption<Self::MOp>> {
|
||||
UnitMOp::alu_branch_mop(mop)
|
||||
}
|
||||
|
|
@ -628,7 +624,7 @@ impl UnitTrait for AluBranch {
|
|||
fn unit_to_reg_alloc(
|
||||
&self,
|
||||
this: Expr<Self::Type>,
|
||||
) -> Expr<UnitToRegAlloc<Self::MOp, Self::ExtraOut, DynSize, DynSize, DynSize>> {
|
||||
) -> Expr<UnitToRegAlloc<PhantomConst<CpuConfig>, Self::MOp, Self::ExtraOut>> {
|
||||
this.unit_to_reg_alloc
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See Notices.txt for copyright information
|
||||
|
||||
use crate::{
|
||||
config::CpuConfig,
|
||||
config::{CpuConfig, CpuConfigUnitCount, PhantomConstCpuConfig},
|
||||
instruction::{COMMON_MOP_SRC_LEN, MOpTrait, PRegNum, UnitNum, UnitOutRegNum},
|
||||
register::PRegValue,
|
||||
unit::{UnitCancelInput, UnitOutput, UnitOutputWrite},
|
||||
|
|
@ -15,13 +15,11 @@ use fayalite::{
|
|||
ty::StaticType,
|
||||
util::ready_valid::ReadyValid,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[hdl]
|
||||
pub struct UnitForwardingInfo<UnitNumWidth: Size, OutRegNumWidth: Size, UnitCount: Size> {
|
||||
pub unit_output_writes: ArrayType<HdlOption<UnitOutputWrite<OutRegNumWidth>>, UnitCount>,
|
||||
pub unit_reg_frees: ArrayType<HdlOption<UnitOutRegNum<OutRegNumWidth>>, UnitCount>,
|
||||
pub _phantom: PhantomData<UnitNumWidth>,
|
||||
#[hdl(no_static)]
|
||||
pub struct UnitForwardingInfo<C: PhantomConstGet<CpuConfig>> {
|
||||
pub unit_output_writes: ArrayType<HdlOption<UnitOutputWrite<C>>, CpuConfigUnitCount<C>>,
|
||||
pub unit_reg_frees: ArrayType<HdlOption<UnitOutRegNum<C>>, CpuConfigUnitCount<C>>,
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
|
|
@ -30,26 +28,18 @@ pub struct UnitInput<MOp: Type> {
|
|||
pub pc: UInt<64>,
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
pub struct UnitToRegAlloc<
|
||||
MOp: Type,
|
||||
ExtraOut: Type,
|
||||
UnitNumWidth: Size,
|
||||
OutRegNumWidth: Size,
|
||||
UnitCount: Size,
|
||||
> {
|
||||
#[hdl(no_static)]
|
||||
pub struct UnitToRegAlloc<C: PhantomConstGet<CpuConfig>, MOp: Type, ExtraOut: Type> {
|
||||
#[hdl(flip)]
|
||||
pub unit_forwarding_info: UnitForwardingInfo<UnitNumWidth, OutRegNumWidth, UnitCount>,
|
||||
pub unit_forwarding_info: UnitForwardingInfo<C>,
|
||||
#[hdl(flip)]
|
||||
pub input: ReadyValid<UnitInput<MOp>>,
|
||||
#[hdl(flip)]
|
||||
pub cancel_input: HdlOption<UnitCancelInput<OutRegNumWidth>>,
|
||||
pub output: HdlOption<UnitOutput<OutRegNumWidth, ExtraOut>>,
|
||||
pub cancel_input: HdlOption<UnitCancelInput<C>>,
|
||||
pub output: HdlOption<UnitOutput<C, ExtraOut>>,
|
||||
}
|
||||
|
||||
impl<MOp: Type, ExtraOut: Type, UnitNumWidth: Size, OutRegNumWidth: Size, UnitCount: Size>
|
||||
UnitToRegAlloc<MOp, ExtraOut, UnitNumWidth, OutRegNumWidth, UnitCount>
|
||||
{
|
||||
impl<C: PhantomConstCpuConfig, MOp: Type, ExtraOut: Type> UnitToRegAlloc<C, MOp, ExtraOut> {
|
||||
pub fn mop_ty(self) -> MOp {
|
||||
self.input.data.HdlSome.mop
|
||||
}
|
||||
|
|
@ -58,16 +48,20 @@ impl<MOp: Type, ExtraOut: Type, UnitNumWidth: Size, OutRegNumWidth: Size, UnitCo
|
|||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
pub struct ExecuteStart<MOp: Type + MOpTrait<DestReg = UnitOutRegNum<DynSize>>> {
|
||||
#[hdl(no_static)]
|
||||
pub struct ExecuteStart<
|
||||
C: PhantomConstGet<CpuConfig>,
|
||||
MOp: Type + MOpTrait<DestReg = UnitOutRegNum<C>>,
|
||||
> {
|
||||
pub mop: MOp,
|
||||
pub pc: UInt<64>,
|
||||
pub src_values: Array<PRegValue, { COMMON_MOP_SRC_LEN }>,
|
||||
pub config: C,
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
pub struct ExecuteEnd<OutRegNumWidth: Size, ExtraOut> {
|
||||
pub unit_output: UnitOutput<OutRegNumWidth, ExtraOut>,
|
||||
#[hdl(no_static)]
|
||||
pub struct ExecuteEnd<C: PhantomConstGet<CpuConfig>, ExtraOut> {
|
||||
pub unit_output: UnitOutput<C, ExtraOut>,
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
|
|
@ -240,10 +234,10 @@ impl InFlightOpsSummary<DynSize> {
|
|||
|
||||
#[hdl_module]
|
||||
pub fn unit_base<
|
||||
MOp: Type + MOpTrait<DestReg = UnitOutRegNum<DynSize>, SrcRegWidth = DynSize>,
|
||||
MOp: Type + MOpTrait<DestReg = UnitOutRegNum<PhantomConst<CpuConfig>>, SrcRegWidth = DynSize>,
|
||||
ExtraOut: Type,
|
||||
>(
|
||||
config: &CpuConfig,
|
||||
config: PhantomConst<CpuConfig>,
|
||||
unit_index: usize,
|
||||
mop_ty: MOp,
|
||||
extra_out_ty: ExtraOut,
|
||||
|
|
@ -251,17 +245,18 @@ pub fn unit_base<
|
|||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let unit_to_reg_alloc: UnitToRegAlloc<MOp, ExtraOut, DynSize, DynSize, DynSize> =
|
||||
m.output(config.unit_to_reg_alloc(mop_ty, extra_out_ty));
|
||||
let unit_to_reg_alloc: UnitToRegAlloc<PhantomConst<CpuConfig>, MOp, ExtraOut> =
|
||||
m.output(UnitToRegAlloc[config][mop_ty][extra_out_ty]);
|
||||
#[hdl]
|
||||
let execute_start: ReadyValid<ExecuteStart<MOp>> = m.output(ReadyValid[ExecuteStart[mop_ty]]);
|
||||
let execute_start: ReadyValid<ExecuteStart<PhantomConst<CpuConfig>, MOp>> =
|
||||
m.output(ReadyValid[ExecuteStart[config][mop_ty]]);
|
||||
#[hdl]
|
||||
let execute_end: HdlOption<ExecuteEnd<DynSize, ExtraOut>> =
|
||||
m.input(HdlOption[ExecuteEnd[config.out_reg_num_width][extra_out_ty]]);
|
||||
let execute_end: HdlOption<ExecuteEnd<PhantomConst<CpuConfig>, ExtraOut>> =
|
||||
m.input(HdlOption[ExecuteEnd[config][extra_out_ty]]);
|
||||
|
||||
connect(execute_start.data, execute_start.ty().data.HdlNone());
|
||||
|
||||
let max_in_flight = config.unit_max_in_flight(unit_index).get();
|
||||
let max_in_flight = config.get().unit_max_in_flight(unit_index).get();
|
||||
let in_flight_op_ty = InFlightOp[mop_ty];
|
||||
#[hdl]
|
||||
let in_flight_ops = reg_builder()
|
||||
|
|
@ -279,16 +274,15 @@ pub fn unit_base<
|
|||
);
|
||||
|
||||
#[hdl]
|
||||
let UnitForwardingInfo::<_, _, _> {
|
||||
let UnitForwardingInfo::<_> {
|
||||
unit_output_writes,
|
||||
unit_reg_frees,
|
||||
_phantom: _,
|
||||
} = unit_to_reg_alloc.unit_forwarding_info;
|
||||
#[hdl]
|
||||
let read_src_regs = wire(mop_ty.src_regs_ty());
|
||||
connect(
|
||||
read_src_regs,
|
||||
repeat(config.p_reg_num().const_zero().cast_to_bits(), ConstUsize),
|
||||
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
|
||||
);
|
||||
#[hdl]
|
||||
let read_src_values = wire();
|
||||
|
|
@ -297,7 +291,7 @@ pub fn unit_base<
|
|||
let input_src_regs = wire(mop_ty.src_regs_ty());
|
||||
connect(
|
||||
input_src_regs,
|
||||
repeat(config.p_reg_num().const_zero().cast_to_bits(), ConstUsize),
|
||||
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
|
||||
);
|
||||
#[hdl]
|
||||
let input_src_regs_valid = wire();
|
||||
|
|
@ -309,7 +303,7 @@ pub fn unit_base<
|
|||
Bool,
|
||||
SourceLocation::caller(),
|
||||
);
|
||||
mem.depth(1 << config.out_reg_num_width);
|
||||
mem.depth(1 << config.get().out_reg_num_width);
|
||||
mem
|
||||
})
|
||||
.collect();
|
||||
|
|
@ -319,11 +313,11 @@ pub fn unit_base<
|
|||
PRegValue,
|
||||
SourceLocation::caller(),
|
||||
);
|
||||
unit_output_regs.depth(1 << config.out_reg_num_width);
|
||||
unit_output_regs.depth(1 << config.get().out_reg_num_width);
|
||||
|
||||
for src_index in 0..COMMON_MOP_SRC_LEN {
|
||||
let read_port = unit_output_regs.new_read_port();
|
||||
let p_reg_num = read_src_regs[src_index].cast_bits_to(config.p_reg_num());
|
||||
let p_reg_num = read_src_regs[src_index].cast_bits_to(PRegNum[config]);
|
||||
connect_any(read_port.addr, p_reg_num.unit_out_reg.value);
|
||||
connect(read_port.en, false);
|
||||
connect(read_port.clk, cd.clk);
|
||||
|
|
@ -336,7 +330,7 @@ pub fn unit_base<
|
|||
|
||||
for src_index in 0..COMMON_MOP_SRC_LEN {
|
||||
let read_port = unit_output_regs_valid[unit_index].new_read_port();
|
||||
let p_reg_num = input_src_regs[src_index].cast_bits_to(config.p_reg_num());
|
||||
let p_reg_num = input_src_regs[src_index].cast_bits_to(PRegNum[config]);
|
||||
connect_any(read_port.addr, p_reg_num.unit_out_reg.value);
|
||||
connect(read_port.en, false);
|
||||
connect(read_port.clk, cd.clk);
|
||||
|
|
@ -367,8 +361,8 @@ pub fn unit_base<
|
|||
connect_any(ready_write_port.addr, unit_output_write.which.value);
|
||||
connect(ready_write_port.en, true);
|
||||
let p_reg_num = #[hdl]
|
||||
PRegNum::<_, _> {
|
||||
unit_num: config.unit_num().from_index(unit_index),
|
||||
PRegNum::<_> {
|
||||
unit_num: UnitNum[config].from_index(unit_index),
|
||||
unit_out_reg: unit_output_write.which,
|
||||
};
|
||||
for src_index in 0..COMMON_MOP_SRC_LEN {
|
||||
|
|
@ -399,10 +393,11 @@ pub fn unit_base<
|
|||
execute_start.data,
|
||||
HdlSome(
|
||||
#[hdl]
|
||||
ExecuteStart::<_> {
|
||||
ExecuteStart::<_, _> {
|
||||
mop: in_flight_op.mop,
|
||||
pc: in_flight_op.pc,
|
||||
src_values: read_src_values,
|
||||
config,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
@ -425,7 +420,7 @@ pub fn unit_base<
|
|||
let input_mop_src_regs = wire(mop_ty.src_regs_ty());
|
||||
connect(
|
||||
input_mop_src_regs,
|
||||
repeat(config.p_reg_num().const_zero().cast_to_bits(), ConstUsize),
|
||||
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
|
||||
);
|
||||
MOp::connect_src_regs(mop, input_mop_src_regs);
|
||||
let src_ready_flags = wire_with_loc(
|
||||
|
|
@ -497,7 +492,7 @@ pub fn unit_base<
|
|||
);
|
||||
connect(
|
||||
src_regs,
|
||||
repeat(config.p_reg_num().const_zero().cast_to_bits(), ConstUsize),
|
||||
repeat(PRegNum[config].const_zero().cast_to_bits(), ConstUsize),
|
||||
);
|
||||
MOp::connect_src_regs(mop, src_regs);
|
||||
|
||||
|
|
@ -521,8 +516,8 @@ pub fn unit_base<
|
|||
value: _,
|
||||
} = unit_output_write;
|
||||
let p_reg_num = #[hdl]
|
||||
PRegNum::<_, _> {
|
||||
unit_num: config.unit_num().from_index(unit_index),
|
||||
PRegNum::<_> {
|
||||
unit_num: UnitNum[config].from_index(unit_index),
|
||||
unit_out_reg,
|
||||
};
|
||||
for src_index in 0..COMMON_MOP_SRC_LEN {
|
||||
|
|
|
|||
17477
crates/cpu/tests/expected/fetch.vcd
generated
17477
crates/cpu/tests/expected/fetch.vcd
generated
File diff suppressed because it is too large
Load diff
156911
crates/cpu/tests/expected/main_memory_and_io.vcd
generated
Normal file
156911
crates/cpu/tests/expected/main_memory_and_io.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
152175
crates/cpu/tests/expected/memory_interface_adapter_no_split.vcd
generated
Normal file
152175
crates/cpu/tests/expected/memory_interface_adapter_no_split.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
146218
crates/cpu/tests/expected/next_pc.vcd
generated
146218
crates/cpu/tests/expected/next_pc.vcd
generated
File diff suppressed because it is too large
Load diff
78659
crates/cpu/tests/expected/receiver_and_uart_clock_gen.vcd
generated
Normal file
78659
crates/cpu/tests/expected/receiver_and_uart_clock_gen.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
137539
crates/cpu/tests/expected/receiver_no_queue_and_uart_clock_gen.vcd
generated
Normal file
137539
crates/cpu/tests/expected/receiver_no_queue_and_uart_clock_gen.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
73376
crates/cpu/tests/expected/reg_alloc.vcd
generated
73376
crates/cpu/tests/expected/reg_alloc.vcd
generated
File diff suppressed because it is too large
Load diff
114674
crates/cpu/tests/expected/simple_uart.vcd
generated
Normal file
114674
crates/cpu/tests/expected/simple_uart.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
14812
crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd
generated
Normal file
14812
crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -5,8 +5,9 @@ use cpu::{
|
|||
config::{CpuConfig, UnitConfig},
|
||||
fetch::{FetchToDecodeInterface, FetchToDecodeInterfaceInner, fetch},
|
||||
main_memory_and_io::{
|
||||
MemoryInterface, MemoryOperationErrorKind, MemoryOperationFinish,
|
||||
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
|
||||
CpuConfigFetchMemoryInterfaceConfig, MemoryInterface, MemoryInterfaceConfig,
|
||||
MemoryOperationErrorKind, MemoryOperationFinish, MemoryOperationFinishKind,
|
||||
MemoryOperationKind, MemoryOperationStart,
|
||||
},
|
||||
next_pc::{FETCH_BLOCK_ID_WIDTH, NextPcToFetchInterface, NextPcToFetchInterfaceInner},
|
||||
unit::UnitKind,
|
||||
|
|
@ -92,8 +93,8 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
|
|||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
|
||||
m.input(MemoryInterface[config]);
|
||||
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
|
||||
m.input(MemoryInterface[CpuConfigFetchMemoryInterfaceConfig[config]]);
|
||||
#[hdl]
|
||||
let queue_debug: ArrayVec<MemoryQueueEntry, ConstUsize<{ MEMORY_QUEUE_SIZE }>> = m.output();
|
||||
m.register_clock_for_past(cd.clk);
|
||||
|
|
@ -109,13 +110,12 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
|
|||
let MemoryInterface::<_> {
|
||||
start,
|
||||
finish,
|
||||
next_fetch_block_ids,
|
||||
next_op_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(next_op_ids, next_op_ids.ty().HdlNone()).await;
|
||||
sim.write(
|
||||
queue_debug,
|
||||
queue_debug.ty().new_sim(MemoryQueueEntry.default_sim()),
|
||||
|
|
@ -130,7 +130,7 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
|
|||
#[hdl]
|
||||
async fn run_fn(
|
||||
cd: Expr<ClockDomain>,
|
||||
memory_interface: Expr<MemoryInterface<PhantomConst<CpuConfig>>>,
|
||||
memory_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
|
||||
queue_debug: Expr<ArrayVec<MemoryQueueEntry, ConstUsize<{ MEMORY_QUEUE_SIZE }>>>,
|
||||
random: &RefCell<Random>,
|
||||
mut sim: ExternModuleSimulationState,
|
||||
|
|
@ -138,7 +138,7 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
|
|||
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 next_op_ids_ty = memory_interface.next_op_ids.ty();
|
||||
let mut queue: VecDeque<SimValue<MemoryQueueEntry>> = VecDeque::new();
|
||||
loop {
|
||||
for entry in &mut queue {
|
||||
|
|
@ -156,12 +156,17 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
|
|||
)
|
||||
.await;
|
||||
sim.write(
|
||||
memory_interface.next_fetch_block_ids,
|
||||
memory_interface.next_op_ids,
|
||||
#[hdl(sim)]
|
||||
next_fetch_block_ids_ty.HdlSome(
|
||||
next_fetch_block_ids_ty
|
||||
next_op_ids_ty.HdlSome(
|
||||
next_op_ids_ty
|
||||
.HdlSome
|
||||
.from_iter_sim(0u8, queue.iter().map(|entry| &entry.fetch_block_id))
|
||||
.from_iter_sim(
|
||||
0u8,
|
||||
queue
|
||||
.iter()
|
||||
.map(|entry| SimValue::into_dyn_int(entry.fetch_block_id.clone())),
|
||||
)
|
||||
.ok()
|
||||
.expect("queue is known to be small enough"),
|
||||
),
|
||||
|
|
@ -235,7 +240,7 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
|
|||
addr,
|
||||
write_data: _,
|
||||
rw_mask,
|
||||
fetch_block_id,
|
||||
op_id,
|
||||
config: _,
|
||||
} = memory_operation_start;
|
||||
#[hdl(sim)]
|
||||
|
|
@ -250,7 +255,7 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
|
|||
let entry = #[hdl(sim)]
|
||||
MemoryQueueEntry {
|
||||
addr,
|
||||
fetch_block_id,
|
||||
fetch_block_id: SimValue::from_dyn_int(op_id),
|
||||
cycles_left: MemoryQueueEntry::get_next_delay(&mut random),
|
||||
};
|
||||
println!("mock memory start: {entry:#?}");
|
||||
|
|
|
|||
305
crates/cpu/tests/main_memory_and_io.rs
Normal file
305
crates/cpu/tests/main_memory_and_io.rs
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use cpu::{
|
||||
main_memory_and_io::{
|
||||
AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind,
|
||||
MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind,
|
||||
MemoryOperationStart, main_memory_and_io, simple_uart::SIMPLE_UART_TRANSMIT_OFFSET,
|
||||
},
|
||||
next_pc::FETCH_BLOCK_ID_WIDTH,
|
||||
};
|
||||
use fayalite::{prelude::*, sim::vcd::VcdWriterDecls, util::RcWriter};
|
||||
use std::num::{NonZeroU64, NonZeroUsize, Wrapping};
|
||||
|
||||
const CLOCK_FREQUENCY: f64 = 2_000_000.0;
|
||||
|
||||
const fn half_period(frequency: f64) -> SimDuration {
|
||||
SimDuration::from_attos((SimDuration::from_secs(1).as_attos() as f64 / frequency / 2.0) as u128)
|
||||
}
|
||||
|
||||
const CLOCK_HALF_PERIOD: SimDuration = half_period(CLOCK_FREQUENCY);
|
||||
|
||||
fn clock_input_properties() -> PhantomConst<peripherals::ClockInputProperties> {
|
||||
peripherals::ClockInput::new(CLOCK_FREQUENCY).properties
|
||||
}
|
||||
|
||||
const SRAM_START: u64 = 0x1000;
|
||||
const SRAM_SIZE: NonZeroU64 = NonZeroU64::new(0x30).unwrap();
|
||||
const UART_START: u64 = 0x100000;
|
||||
const UART_BAUD_RATE: f64 = 115200.0;
|
||||
const UART_RECEIVER_QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(16).unwrap();
|
||||
const SRAM_INITIAL_CONTENTS: &[u8; SRAM_SIZE.get() as usize] =
|
||||
b"This is a main memory and I/O test. ";
|
||||
const SRAM_WRITING_CONTENTS: &[u8; SRAM_SIZE.get() as usize] =
|
||||
b"Testing. Hello World. abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
const CONFIG: MemoryInterfaceConfig =
|
||||
MemoryInterfaceConfig::new(3, 8, FETCH_BLOCK_ID_WIDTH, AddressRange::Full);
|
||||
const BUS_WIDTH_IN_BYTES: usize = CONFIG.bus_width_in_bytes();
|
||||
|
||||
fn config() -> PhantomConst<MemoryInterfaceConfig> {
|
||||
PhantomConst::new_sized(CONFIG)
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
fn test_harness() {
|
||||
let config = config();
|
||||
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
|
||||
m.input(MemoryInterface[config]);
|
||||
|
||||
#[hdl]
|
||||
let dut = instance(main_memory_and_io(
|
||||
config,
|
||||
clock_input_properties(),
|
||||
AddressRange::Limited {
|
||||
start: Wrapping(SRAM_START),
|
||||
size: SRAM_SIZE,
|
||||
},
|
||||
SRAM_INITIAL_CONTENTS,
|
||||
UART_START,
|
||||
UART_BAUD_RATE,
|
||||
UART_RECEIVER_QUEUE_SIZE,
|
||||
));
|
||||
connect(dut.cd, cd);
|
||||
connect(dut.memory_interface, memory_interface);
|
||||
connect(dut.uart.rx, dut.uart.tx); // loop back for testing
|
||||
}
|
||||
|
||||
struct MainMemoryAndIoTester {
|
||||
sim: Simulation<test_harness>,
|
||||
next_op_id: u64,
|
||||
}
|
||||
|
||||
impl MainMemoryAndIoTester {
|
||||
fn reset(&mut self) {
|
||||
let sim = &mut self.sim;
|
||||
sim.write(
|
||||
sim.io().memory_interface.start.data,
|
||||
sim.io().ty().memory_interface.start.data.HdlNone(),
|
||||
);
|
||||
sim.write(sim.io().memory_interface.finish.ready, false);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, false);
|
||||
}
|
||||
fn wait(&mut self, cycles: u64) {
|
||||
let sim = &mut self.sim;
|
||||
sim.write(
|
||||
sim.io().memory_interface.start.data,
|
||||
sim.io().ty().memory_interface.start.data.HdlNone(),
|
||||
);
|
||||
sim.write(sim.io().memory_interface.finish.ready, false);
|
||||
for _ in 0..cycles {
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
}
|
||||
}
|
||||
#[track_caller]
|
||||
#[hdl]
|
||||
fn try_rw_op(
|
||||
&mut self,
|
||||
addr: u64,
|
||||
write: Option<[u8; BUS_WIDTH_IN_BYTES]>,
|
||||
rw_mask: [bool; BUS_WIDTH_IN_BYTES],
|
||||
max_wait: u64,
|
||||
) -> Result<[u8; BUS_WIDTH_IN_BYTES], SimValue<MemoryOperationErrorKind>> {
|
||||
let config = config();
|
||||
let sim = &mut self.sim;
|
||||
let op_id = self.next_op_id.cast_to(UInt[FETCH_BLOCK_ID_WIDTH]);
|
||||
self.next_op_id += 1;
|
||||
sim.write(
|
||||
sim.io().memory_interface.start.data,
|
||||
#[hdl(sim)]
|
||||
(sim.io().ty().memory_interface.start.data).HdlSome(match write {
|
||||
Some(write) =>
|
||||
{
|
||||
#[hdl(sim)]
|
||||
MemoryOperationStart::<_> {
|
||||
kind: #[hdl(sim)]
|
||||
MemoryOperationKind.Write(),
|
||||
addr,
|
||||
write_data: &write[..],
|
||||
rw_mask: &rw_mask[..],
|
||||
op_id,
|
||||
config,
|
||||
}
|
||||
}
|
||||
None =>
|
||||
{
|
||||
#[hdl(sim)]
|
||||
MemoryOperationStart::<_> {
|
||||
kind: #[hdl(sim)]
|
||||
MemoryOperationKind.Read(),
|
||||
addr,
|
||||
write_data: &[0u8; BUS_WIDTH_IN_BYTES][..],
|
||||
rw_mask: &rw_mask[..],
|
||||
op_id,
|
||||
config,
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
sim.write(sim.io().memory_interface.finish.ready, true);
|
||||
let mut cleared_start = false;
|
||||
for _ in 0..max_wait {
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
let start_ready = sim.read_bool(sim.io().memory_interface.start.ready);
|
||||
let finish_data = sim.read(sim.io().memory_interface.finish.data);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
if start_ready && !cleared_start {
|
||||
cleared_start = true;
|
||||
sim.write(
|
||||
sim.io().memory_interface.start.data,
|
||||
sim.io().ty().memory_interface.start.data.HdlNone(),
|
||||
);
|
||||
}
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(finish) = finish_data {
|
||||
#[hdl(sim)]
|
||||
let MemoryOperationFinish::<_> {
|
||||
kind,
|
||||
read_data,
|
||||
config: _,
|
||||
} = finish;
|
||||
#[hdl(sim)]
|
||||
match kind {
|
||||
MemoryOperationFinishKind::Success(_) => {
|
||||
return Ok(std::array::from_fn(|i| read_data[i].as_int()));
|
||||
}
|
||||
MemoryOperationFinishKind::Error(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
panic!("waited too many cycles");
|
||||
}
|
||||
#[track_caller]
|
||||
fn try_read<const N: usize>(
|
||||
&mut self,
|
||||
addr: u64,
|
||||
max_wait: u64,
|
||||
) -> Result<[u8; N], SimValue<MemoryOperationErrorKind>> {
|
||||
const { assert!(N <= BUS_WIDTH_IN_BYTES) }
|
||||
let bus_addr = addr - addr % BUS_WIDTH_IN_BYTES as u64;
|
||||
let mut rw_mask = [false; BUS_WIDTH_IN_BYTES];
|
||||
let start = (addr % BUS_WIDTH_IN_BYTES as u64) as usize;
|
||||
rw_mask[start..start + N].fill(true);
|
||||
self.try_rw_op(bus_addr, None, rw_mask, max_wait)
|
||||
.map(|v| std::array::from_fn(|i| v[i + start]))
|
||||
}
|
||||
#[track_caller]
|
||||
fn read<const N: usize>(&mut self, addr: u64, max_wait: u64) -> [u8; N] {
|
||||
self.try_read(addr, max_wait).expect("read returned error")
|
||||
}
|
||||
#[track_caller]
|
||||
fn try_write<const N: usize>(
|
||||
&mut self,
|
||||
addr: u64,
|
||||
data: [u8; N],
|
||||
max_wait: u64,
|
||||
) -> Result<(), SimValue<MemoryOperationErrorKind>> {
|
||||
const { assert!(N <= BUS_WIDTH_IN_BYTES) }
|
||||
let bus_addr = addr - addr % BUS_WIDTH_IN_BYTES as u64;
|
||||
let mut rw_mask = [false; BUS_WIDTH_IN_BYTES];
|
||||
let mut write_data = [0u8; BUS_WIDTH_IN_BYTES];
|
||||
let start = (addr % BUS_WIDTH_IN_BYTES as u64) as usize;
|
||||
rw_mask[start..start + N].fill(true);
|
||||
write_data[start..start + N].copy_from_slice(&data);
|
||||
self.try_rw_op(bus_addr, Some(write_data), rw_mask, max_wait)?;
|
||||
Ok(())
|
||||
}
|
||||
#[track_caller]
|
||||
fn write<const N: usize>(&mut self, addr: u64, data: [u8; N], max_wait: u64) {
|
||||
self.try_write(addr, data, max_wait)
|
||||
.expect("read returned error")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[hdl]
|
||||
fn test_main_memory_and_io() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
let mut sim = Simulation::new(test_harness());
|
||||
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 mut tester = MainMemoryAndIoTester { sim, next_op_id: 0 };
|
||||
tester.reset();
|
||||
|
||||
const UART_MESSAGE: &str = "Test.";
|
||||
for b in UART_MESSAGE.bytes() {
|
||||
tester.write(UART_START + SIMPLE_UART_TRANSMIT_OFFSET, [b], 200);
|
||||
}
|
||||
|
||||
for (i, expected_chunk) in SRAM_INITIAL_CONTENTS
|
||||
.as_chunks::<BUS_WIDTH_IN_BYTES>()
|
||||
.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let addr = (i as u64 * BUS_WIDTH_IN_BYTES as u64) + SRAM_START;
|
||||
let chunk = tester.read::<BUS_WIDTH_IN_BYTES>(addr, 10);
|
||||
assert_eq!(chunk, *expected_chunk);
|
||||
}
|
||||
|
||||
for (i, chunk) in SRAM_WRITING_CONTENTS
|
||||
.as_chunks::<BUS_WIDTH_IN_BYTES>()
|
||||
.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let addr = (i as u64 * BUS_WIDTH_IN_BYTES as u64) + SRAM_START;
|
||||
tester.write(addr, *chunk, 10);
|
||||
}
|
||||
|
||||
for (i, expected_chunk) in SRAM_WRITING_CONTENTS
|
||||
.as_chunks::<BUS_WIDTH_IN_BYTES>()
|
||||
.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let addr = (i as u64 * BUS_WIDTH_IN_BYTES as u64) + SRAM_START;
|
||||
let chunk = tester.read::<BUS_WIDTH_IN_BYTES>(addr, 10);
|
||||
assert_eq!(chunk, *expected_chunk);
|
||||
}
|
||||
|
||||
tester.wait(400);
|
||||
|
||||
for expected_byte in UART_MESSAGE.bytes() {
|
||||
let [byte] = tester.read(UART_START + SIMPLE_UART_TRANSMIT_OFFSET, 10);
|
||||
assert_eq!(byte, expected_byte);
|
||||
}
|
||||
|
||||
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
if vcd != include_str!("expected/main_memory_and_io.vcd") {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
750
crates/cpu/tests/memory_interface.rs
Normal file
750
crates/cpu/tests/memory_interface.rs
Normal file
|
|
@ -0,0 +1,750 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use cpu::{
|
||||
main_memory_and_io::{
|
||||
AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind,
|
||||
MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind,
|
||||
MemoryOperationStart, memory_interface_adapter_no_split,
|
||||
},
|
||||
next_pc::FETCH_BLOCK_ID_WIDTH,
|
||||
util::array_vec::ArrayVec,
|
||||
};
|
||||
use fayalite::{
|
||||
bundle::{BundleField, BundleType},
|
||||
intern::{Intern, Interned},
|
||||
module::instance_with_loc,
|
||||
prelude::*,
|
||||
sim::vcd::VcdWriterDecls,
|
||||
util::RcWriter,
|
||||
};
|
||||
use std::{cell::Cell, collections::VecDeque, fmt};
|
||||
|
||||
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(0x8c16a62518f86883) // random prime
|
||||
.rotate_left(32)
|
||||
.wrapping_mul(0xf807b7df2082353d) // random prime
|
||||
.rotate_right(60);
|
||||
const DELAYS: &[u8; 0x20] = &[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, //
|
||||
1, 1, 1, 1, 1, 1, 1, 1, //
|
||||
2, 2, 2, 2, 2, 2, 2, 2, //
|
||||
3, 3, 3, 3, 4, 5, 6, 20, //
|
||||
];
|
||||
DELAYS[(random & 0x1F) as usize]
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn mock_memory(memory: Memory) {
|
||||
let Memory { config, contents } = memory;
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let input_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
|
||||
m.input(MemoryInterface[config]);
|
||||
m.register_clock_for_past(cd.clk);
|
||||
#[hdl]
|
||||
async fn run(
|
||||
cd: Expr<ClockDomain>,
|
||||
input_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
|
||||
config: PhantomConst<MemoryInterfaceConfig>,
|
||||
contents: Interned<str>,
|
||||
delay_sequence_index: &Cell<u64>,
|
||||
mut sim: ExternModuleSimulationState,
|
||||
) {
|
||||
#[derive(Debug)]
|
||||
struct Op {
|
||||
cycles_left: u8,
|
||||
op_id: SimValue<UInt>,
|
||||
finish: SimValue<MemoryOperationFinish<PhantomConst<MemoryInterfaceConfig>>>,
|
||||
}
|
||||
let mut ops = VecDeque::<Op>::with_capacity(config.get().queue_capacity.get());
|
||||
let finish_ty = input_interface.ty().finish.data.HdlSome;
|
||||
loop {
|
||||
for op in &mut ops {
|
||||
op.cycles_left = op.cycles_left.saturating_sub(1);
|
||||
}
|
||||
let next_op_ids_ty = input_interface.ty().next_op_ids.HdlSome;
|
||||
sim.write(
|
||||
input_interface.next_op_ids,
|
||||
#[hdl(sim)]
|
||||
(input_interface.ty().next_op_ids).HdlSome(
|
||||
next_op_ids_ty
|
||||
.from_iter_sim(
|
||||
next_op_ids_ty.element().zero(),
|
||||
ops.iter().map(|op| &op.op_id),
|
||||
)
|
||||
.expect("known to fit"),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
if let Some(Op {
|
||||
cycles_left: 0,
|
||||
op_id: _,
|
||||
finish,
|
||||
}) = ops.front()
|
||||
{
|
||||
sim.write(
|
||||
input_interface.finish.data,
|
||||
#[hdl(sim)]
|
||||
(input_interface.ty().finish.data).HdlSome(finish),
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
sim.write(
|
||||
input_interface.finish.data,
|
||||
#[hdl(sim)]
|
||||
(input_interface.ty().finish.data).HdlNone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
sim.write_bool(
|
||||
input_interface.start.ready,
|
||||
ops.len() < config.get().queue_capacity.get(),
|
||||
)
|
||||
.await;
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
if sim
|
||||
.read_past_bool(input_interface.finish.ready, cd.clk)
|
||||
.await
|
||||
{
|
||||
dbg!(ops.pop_front_if(|op| op.cycles_left == 0));
|
||||
}
|
||||
if sim
|
||||
.read_past_bool(input_interface.start.ready, cd.clk)
|
||||
.await
|
||||
{
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(start) = sim.read_past(input_interface.start.data, cd.clk).await {
|
||||
#[hdl(sim)]
|
||||
let MemoryOperationStart::<_> {
|
||||
kind,
|
||||
addr,
|
||||
write_data,
|
||||
rw_mask,
|
||||
op_id,
|
||||
config: _,
|
||||
} = start;
|
||||
let mut error = false;
|
||||
let mut read_data = vec![0u8; finish_ty.read_data.len()];
|
||||
#[hdl(sim)]
|
||||
match &kind {
|
||||
MemoryOperationKind::Read => {
|
||||
for (i, v) in read_data.iter_mut().enumerate() {
|
||||
if *rw_mask[i] {
|
||||
let addr = addr.as_int().wrapping_add(i as u64);
|
||||
let offset =
|
||||
addr.wrapping_sub(config.get().address_range.start().0);
|
||||
if !config.get().address_range.contains(addr)
|
||||
|| offset >= contents.len() as u64
|
||||
{
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
*v = contents.as_bytes()[offset as usize];
|
||||
println!(
|
||||
"reading byte at {addr:#x} (offset={offset:#x}) -> {v:#x} (contents={contents:?})",
|
||||
);
|
||||
}
|
||||
}
|
||||
if !error {
|
||||
println!(
|
||||
"read chunk at {addr:#x}: {:#x?}",
|
||||
std::fmt::from_fn(|f| f
|
||||
.debug_map()
|
||||
.entries(
|
||||
read_data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, &v)| rw_mask[i].then(|| (i, v)))
|
||||
)
|
||||
.finish()),
|
||||
addr = addr.as_int(),
|
||||
);
|
||||
}
|
||||
}
|
||||
MemoryOperationKind::Write => {
|
||||
todo!("write {write_data:?}");
|
||||
}
|
||||
}
|
||||
let finish_kind = if error {
|
||||
println!("error at: {addr} config: {config:?}");
|
||||
read_data.fill(0);
|
||||
#[hdl(sim)]
|
||||
MemoryOperationFinishKind.Error(
|
||||
#[hdl(sim)]
|
||||
MemoryOperationErrorKind.Generic(),
|
||||
)
|
||||
} else {
|
||||
#[hdl(sim)]
|
||||
MemoryOperationFinishKind.Success(kind)
|
||||
};
|
||||
ops.push_back(Op {
|
||||
cycles_left: get_next_delay(delay_sequence_index),
|
||||
op_id,
|
||||
finish: #[hdl(sim)]
|
||||
MemoryOperationFinish::<_> {
|
||||
kind: finish_kind,
|
||||
read_data,
|
||||
config,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.extern_module_simulation_fn(
|
||||
(cd, input_interface, config, contents),
|
||||
async |(cd, input_interface, config, contents), mut sim| {
|
||||
// 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(
|
||||
input_interface.next_op_ids,
|
||||
#[hdl(sim)]
|
||||
(input_interface.ty().next_op_ids).HdlNone(),
|
||||
)
|
||||
.await;
|
||||
sim.write_bool(input_interface.start.ready, false).await;
|
||||
sim.write(
|
||||
input_interface.finish.data,
|
||||
#[hdl(sim)]
|
||||
(input_interface.ty().finish.data).HdlNone(),
|
||||
)
|
||||
.await;
|
||||
},
|
||||
|sim, ()| {
|
||||
run(
|
||||
cd,
|
||||
input_interface,
|
||||
config,
|
||||
contents,
|
||||
&delay_sequence_index,
|
||||
sim,
|
||||
)
|
||||
},
|
||||
)
|
||||
.await
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
struct Memory {
|
||||
contents: Interned<str>,
|
||||
config: PhantomConst<MemoryInterfaceConfig>,
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
fn new(
|
||||
contents: impl AsRef<str>,
|
||||
log2_bus_width_in_bytes: u8,
|
||||
address_range: AddressRange,
|
||||
) -> Self {
|
||||
Self {
|
||||
contents: contents.as_ref().intern(),
|
||||
config: PhantomConst::new_sized(MemoryInterfaceConfig::new(
|
||||
log2_bus_width_in_bytes,
|
||||
8,
|
||||
FETCH_BLOCK_ID_WIDTH,
|
||||
address_range,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn mock_cpu(memories: Interned<[Memory]>) {
|
||||
const LOG2_BUS_WIDTH: u8 = 3;
|
||||
const BUS_WIDTH: usize = 1 << LOG2_BUS_WIDTH;
|
||||
let config = PhantomConst::new_sized(MemoryInterfaceConfig::new(
|
||||
LOG2_BUS_WIDTH,
|
||||
8,
|
||||
FETCH_BLOCK_ID_WIDTH,
|
||||
AddressRange::Full,
|
||||
));
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let output_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
|
||||
m.output(MemoryInterface[config]);
|
||||
#[hdl]
|
||||
let finished: Bool = m.output();
|
||||
m.register_clock_for_past(cd.clk);
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
struct Op {
|
||||
addr: u64,
|
||||
read_mask: [bool; BUS_WIDTH],
|
||||
}
|
||||
impl fmt::Debug for Op {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self { addr, read_mask } = self;
|
||||
f.debug_struct("Op")
|
||||
.field("addr", &fmt::from_fn(|f| write!(f, "{addr:#x}")))
|
||||
.field("read_mask", read_mask)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
#[hdl]
|
||||
async fn generator(
|
||||
cd: Expr<ClockDomain>,
|
||||
output_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
|
||||
config: PhantomConst<MemoryInterfaceConfig>,
|
||||
sequence: &[Op],
|
||||
delay_sequence_index: &Cell<u64>,
|
||||
mut sim: ExternModuleSimulationState,
|
||||
) {
|
||||
println!("generator: start");
|
||||
let start_ty = MemoryOperationStart[config];
|
||||
for (op_index, op) in sequence.iter().enumerate() {
|
||||
sim.write(
|
||||
output_interface.start.data,
|
||||
#[hdl(sim)]
|
||||
(output_interface.ty().start.data).HdlNone(),
|
||||
)
|
||||
.await;
|
||||
let delay = get_next_delay(delay_sequence_index);
|
||||
println!("generator: delay by {delay}");
|
||||
for i in 0..delay {
|
||||
println!("generator: delay cycle {i}");
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
}
|
||||
println!("generator: generated {op:?}");
|
||||
sim.write(
|
||||
output_interface.start.data,
|
||||
#[hdl(sim)]
|
||||
(output_interface.ty().start.data).HdlSome(
|
||||
#[hdl(sim)]
|
||||
MemoryOperationStart::<_> {
|
||||
kind: #[hdl(sim)]
|
||||
MemoryOperationKind.Read(),
|
||||
addr: op.addr,
|
||||
write_data: &[0u8; BUS_WIDTH][..],
|
||||
rw_mask: &op.read_mask[..],
|
||||
op_id: op_index.cast_to(start_ty.op_id),
|
||||
config,
|
||||
},
|
||||
),
|
||||
)
|
||||
.await;
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
while !sim
|
||||
.read_past_bool(output_interface.start.ready, cd.clk)
|
||||
.await
|
||||
{
|
||||
println!("generator: start.ready was false");
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
}
|
||||
}
|
||||
sim.write(
|
||||
output_interface.start.data,
|
||||
#[hdl(sim)]
|
||||
(output_interface.ty().start.data).HdlNone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
#[hdl]
|
||||
async fn checker(
|
||||
cd: Expr<ClockDomain>,
|
||||
output_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
|
||||
config: PhantomConst<MemoryInterfaceConfig>,
|
||||
sequence: &[Op],
|
||||
memories: Interned<[Memory]>,
|
||||
delay_sequence_index: &Cell<u64>,
|
||||
sim: &mut ExternModuleSimulationState,
|
||||
) {
|
||||
println!("checker: start");
|
||||
for op in sequence {
|
||||
sim.write_bool(output_interface.finish.ready, false).await;
|
||||
let delay = get_next_delay(delay_sequence_index);
|
||||
println!("checker: delay by {delay}");
|
||||
for i in 0..delay {
|
||||
println!("checker: delay cycle {i}");
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
}
|
||||
sim.write_bool(output_interface.finish.ready, true).await;
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
let mut finish = loop {
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(finish) = sim.read_past(output_interface.finish.data, cd.clk).await {
|
||||
break finish;
|
||||
}
|
||||
println!("checker: finish.data was HdlNone");
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
};
|
||||
println!("checker: checking for {op:?}");
|
||||
let finish_unmasked = finish.clone();
|
||||
for (v, &mask) in finish.read_data.iter_mut().zip(&op.read_mask) {
|
||||
if !mask {
|
||||
*v = 0u8.to_sim_value(); // ignore outputs for ignored bytes
|
||||
}
|
||||
}
|
||||
let mut expected_finish = memories
|
||||
.iter()
|
||||
.find(|m| dbg!(dbg!(m.config.get().address_range).contains(op.addr)))
|
||||
.and_then(
|
||||
|&Memory {
|
||||
config: memory_config,
|
||||
contents,
|
||||
}|
|
||||
-> Option<_> {
|
||||
let mut read_data = [0u8; BUS_WIDTH];
|
||||
let mut first_enabled = None;
|
||||
let mut last_enabled = None;
|
||||
for (i, &mask) in op.read_mask.iter().enumerate() {
|
||||
if mask {
|
||||
first_enabled.get_or_insert(i);
|
||||
last_enabled = Some(i);
|
||||
read_data[i] = *dbg!(
|
||||
dbg!(contents).as_bytes().get(
|
||||
dbg!(usize::try_from(
|
||||
op.addr.wrapping_add(i as u64).wrapping_sub(
|
||||
memory_config.get().address_range.start().0,
|
||||
),
|
||||
))
|
||||
.ok()?,
|
||||
)
|
||||
)?;
|
||||
}
|
||||
}
|
||||
if let (Some(first_enabled), Some(last_enabled)) =
|
||||
(first_enabled, last_enabled)
|
||||
{
|
||||
let log2_bus_width_in_bytes =
|
||||
memory_config.get().log2_bus_width_in_bytes;
|
||||
if first_enabled >> log2_bus_width_in_bytes
|
||||
!= last_enabled >> log2_bus_width_in_bytes
|
||||
{
|
||||
// this operation requires more than one operation at the final memory,
|
||||
// so it gets turned into an error since we're using
|
||||
// memory_interface_adapter_no_split
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(
|
||||
#[hdl(sim)]
|
||||
MemoryOperationFinish::<_> {
|
||||
kind: #[hdl(sim)]
|
||||
MemoryOperationFinishKind.Success(
|
||||
#[hdl(sim)]
|
||||
MemoryOperationKind.Read(),
|
||||
),
|
||||
read_data: &read_data[..],
|
||||
config,
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
#[hdl(sim)]
|
||||
MemoryOperationFinish::<_> {
|
||||
kind: #[hdl(sim)]
|
||||
MemoryOperationFinishKind.Error(
|
||||
#[hdl(sim)]
|
||||
MemoryOperationErrorKind.Generic(),
|
||||
),
|
||||
read_data: &[0u8; BUS_WIDTH][..],
|
||||
config,
|
||||
}
|
||||
});
|
||||
// make SimValue fill in the enum padding so they format the same
|
||||
SimValue::bits_mut(&mut finish);
|
||||
SimValue::bits_mut(&mut expected_finish);
|
||||
assert!(
|
||||
format!("{finish:#?}") == format!("{expected_finish:#?}"),
|
||||
"op={op:#?}\nexpected_finish={expected_finish:#?}\n\
|
||||
finish={finish:#?}\nfinish_unmasked={finish_unmasked:#?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
m.extern_module_simulation_fn(
|
||||
(cd, output_interface, finished, config, memories),
|
||||
async |(cd, output_interface, finished, config, memories), mut sim| {
|
||||
sim.write_bool(finished, false).await;
|
||||
sim.write_bool(output_interface.finish.ready, false).await;
|
||||
sim.write(
|
||||
output_interface.start.data,
|
||||
#[hdl(sim)]
|
||||
(output_interface.ty().start.data).HdlNone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// intentionally have a different sequence each time we're reset
|
||||
let generator_delay_sequence_index = Cell::new(1 << 63);
|
||||
let checker_delay_sequence_index = Cell::new(1 << 62);
|
||||
let mut sequence = Vec::new();
|
||||
sequence.push(Op {
|
||||
addr: !0 << 16,
|
||||
read_mask: [true; _],
|
||||
});
|
||||
for (i, memory) in memories.iter().enumerate() {
|
||||
assert!(
|
||||
memory
|
||||
.config
|
||||
.get()
|
||||
.address_range
|
||||
.start()
|
||||
.0
|
||||
.is_multiple_of(BUS_WIDTH as u64)
|
||||
);
|
||||
assert!(
|
||||
memory
|
||||
.config
|
||||
.get()
|
||||
.address_range
|
||||
.last()
|
||||
.wrapping_add(1)
|
||||
.is_multiple_of(BUS_WIDTH as u64)
|
||||
);
|
||||
if i == 0 {
|
||||
for log2_read_size in 0..=LOG2_BUS_WIDTH {
|
||||
let read_size = 1 << log2_read_size;
|
||||
for offset in (0..BUS_WIDTH).step_by(read_size) {
|
||||
sequence.push(Op {
|
||||
addr: memory.config.get().address_range.start().0,
|
||||
read_mask: std::array::from_fn(|byte_index| {
|
||||
byte_index
|
||||
.checked_sub(offset)
|
||||
.is_some_and(|v| v < read_size)
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (addr, chunk) in (memory.config.get().address_range.start().0..)
|
||||
.step_by(BUS_WIDTH)
|
||||
.zip(memory.contents.as_bytes().chunks(BUS_WIDTH))
|
||||
{
|
||||
let size = memory.config.get().bus_width_in_bytes();
|
||||
for offset in (0..BUS_WIDTH).step_by(size) {
|
||||
let mut op = Op {
|
||||
addr,
|
||||
read_mask: std::array::from_fn(|i| i >= offset && i < offset + size),
|
||||
};
|
||||
op.read_mask[chunk.len()..].fill(false);
|
||||
if op.read_mask.contains(&true) && sequence.last() != Some(&op) {
|
||||
sequence.push(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sequence.extend_from_within(..);
|
||||
sequence.extend_from_within(..);
|
||||
sim.fork_join_scope(async |scope, mut sim| {
|
||||
scope.spawn_detached(async |_scope, mut sim| {
|
||||
sim.resettable(
|
||||
cd,
|
||||
async |mut sim| {
|
||||
sim.write(
|
||||
output_interface.start.data,
|
||||
#[hdl(sim)]
|
||||
(output_interface.ty().start.data).HdlNone(),
|
||||
)
|
||||
.await;
|
||||
},
|
||||
|sim, ()| {
|
||||
generator(
|
||||
cd,
|
||||
output_interface,
|
||||
config,
|
||||
&sequence,
|
||||
&generator_delay_sequence_index,
|
||||
sim,
|
||||
)
|
||||
},
|
||||
)
|
||||
.await
|
||||
});
|
||||
scope.spawn_detached(async |_scope, mut sim| {
|
||||
sim.resettable(
|
||||
cd,
|
||||
async |_| {},
|
||||
async |mut sim, ()| {
|
||||
let mut expected_range = 0u32..0u32;
|
||||
loop {
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
let next_op_ids =
|
||||
sim.read_past(output_interface.next_op_ids, cd.clk).await;
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(next_op_ids) = next_op_ids {
|
||||
let op_id_ty = next_op_ids.ty().element();
|
||||
let capacity = next_op_ids.ty().capacity();
|
||||
let next_op_ids = ArrayVec::elements_sim_ref(&next_op_ids);
|
||||
let expected_next_op_ids = Vec::from_iter(
|
||||
expected_range
|
||||
.clone()
|
||||
.map(|i| i.cast_to(op_id_ty).into_sim_value()),
|
||||
);
|
||||
assert_eq!(
|
||||
next_op_ids,
|
||||
expected_next_op_ids,
|
||||
"{:#x}..{:#x} ({expected_range:?}){}",
|
||||
expected_range.start,
|
||||
expected_range.end,
|
||||
if expected_range.len() > capacity {
|
||||
"\nexpected_range is bigger than capacity"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
);
|
||||
}
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(_) =
|
||||
sim.read_past(output_interface.start.data, cd.clk).await
|
||||
{
|
||||
if sim
|
||||
.read_past_bool(output_interface.start.ready, cd.clk)
|
||||
.await
|
||||
{
|
||||
expected_range.end += 1;
|
||||
}
|
||||
}
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(_) =
|
||||
sim.read_past(output_interface.finish.data, cd.clk).await
|
||||
{
|
||||
if sim
|
||||
.read_past_bool(output_interface.finish.ready, cd.clk)
|
||||
.await
|
||||
{
|
||||
expected_range.start += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
});
|
||||
sim.resettable(
|
||||
cd,
|
||||
async |mut sim| {
|
||||
sim.write_bool(finished, false).await;
|
||||
sim.write_bool(output_interface.finish.ready, false).await;
|
||||
},
|
||||
async |mut sim, ()| {
|
||||
checker(
|
||||
cd,
|
||||
output_interface,
|
||||
config,
|
||||
&sequence,
|
||||
memories,
|
||||
&checker_delay_sequence_index,
|
||||
&mut sim,
|
||||
)
|
||||
.await;
|
||||
sim.write_bool(finished, true).await;
|
||||
loop {
|
||||
sim.write_bool(output_interface.finish.ready, true).await;
|
||||
sim.wait_for_clock_edge(cd.clk).await;
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(finish) =
|
||||
sim.read_past(output_interface.finish.data, cd.clk).await
|
||||
{
|
||||
panic!("spurious finished transaction: {finish:#?}");
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
fn memory_interface_adapter_no_split_dut(memories: Interned<[Memory]>) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let finished: Bool = m.output();
|
||||
#[hdl]
|
||||
let mock_cpu = instance(mock_cpu(memories));
|
||||
connect(mock_cpu.cd, cd);
|
||||
connect(finished, mock_cpu.finished);
|
||||
let (fields, inputs): (Vec<_>, Vec<_>) = memories
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, &memory)| {
|
||||
let mock_mem = instance_with_loc(
|
||||
&format!("mock_mem_{index}"),
|
||||
mock_memory(memory),
|
||||
SourceLocation::caller(),
|
||||
);
|
||||
connect(mock_mem.cd, cd);
|
||||
(
|
||||
BundleField {
|
||||
name: format!("{index}").intern_deref(),
|
||||
flipped: false,
|
||||
ty: MemoryInterface[memory.config].canonical(),
|
||||
},
|
||||
mock_mem.input_interface,
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
let bundle_ty = Bundle::new(fields.intern_deref());
|
||||
#[hdl]
|
||||
let adapter = instance(memory_interface_adapter_no_split(
|
||||
mock_cpu.ty().output_interface.config,
|
||||
bundle_ty,
|
||||
));
|
||||
connect(adapter.cd, cd);
|
||||
connect(adapter.input_interface, mock_cpu.output_interface);
|
||||
for (field, input) in bundle_ty.fields().into_iter().zip(inputs) {
|
||||
connect(input, Expr::field(adapter.output_interfaces, &field.name));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[hdl]
|
||||
fn test_memory_interface_adapter_no_split() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
let memories = vec![
|
||||
Memory::new("Testing", 3, AddressRange::from_range(0x1000..0x2000)),
|
||||
Memory::new("Memory2.", 2, AddressRange::from_range(0x2000..0x2010)),
|
||||
Memory::new("Contents Test", 0, AddressRange::from_range(0x3000..0x3100)),
|
||||
]
|
||||
.intern_deref();
|
||||
let m = memory_interface_adapter_no_split_dut(memories);
|
||||
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..500 {
|
||||
println!("cycle: {cycle}");
|
||||
sim.advance_time(SimDuration::from_nanos(500));
|
||||
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);
|
||||
}
|
||||
sim.advance_time(SimDuration::from_nanos(500));
|
||||
assert!(sim.read_bool(sim.io().finished));
|
||||
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
if vcd != include_str!("expected/memory_interface_adapter_no_split.vcd") {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
980
crates/cpu/tests/simple_uart.rs
Normal file
980
crates/cpu/tests/simple_uart.rs
Normal file
|
|
@ -0,0 +1,980 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use cpu::{
|
||||
main_memory_and_io::{
|
||||
MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind, MemoryOperationFinish,
|
||||
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
|
||||
simple_uart::{
|
||||
ReceiverQueueStatus, SIMPLE_UART_RECEIVE_OFFSET, SIMPLE_UART_STATUS_OFFSET,
|
||||
SIMPLE_UART_TRANSMIT_OFFSET, SIMPLE_UART_USED_SIZE, receiver, receiver_no_queue,
|
||||
simple_uart, simple_uart_memory_interface_config, transmitter, uart_clock_gen,
|
||||
},
|
||||
},
|
||||
next_pc::FETCH_BLOCK_ID_WIDTH,
|
||||
util::array_vec::ArrayVec,
|
||||
};
|
||||
use fayalite::{
|
||||
bundle::BundleType,
|
||||
intern::{Intern, Interned},
|
||||
prelude::*,
|
||||
sim::vcd::VcdWriterDecls,
|
||||
util::{RcWriter, ready_valid::ReadyValid},
|
||||
};
|
||||
use std::{
|
||||
borrow::BorrowMut,
|
||||
marker::PhantomData,
|
||||
num::{NonZeroUsize, Wrapping},
|
||||
};
|
||||
|
||||
const fn half_period(frequency: f64) -> SimDuration {
|
||||
SimDuration::from_attos((SimDuration::from_secs(1).as_attos() as f64 / frequency / 2.0) as u128)
|
||||
}
|
||||
|
||||
const CLOCK_FREQUENCY: f64 = 200_000.0;
|
||||
const CLOCK_HALF_PERIOD: SimDuration = half_period(CLOCK_FREQUENCY);
|
||||
const BAUD_RATE: f64 = 9600.0;
|
||||
const BAUD_HALF_PERIOD: SimDuration = half_period(BAUD_RATE);
|
||||
const BAUD_PERIOD: SimDuration = BAUD_HALF_PERIOD.saturating_add(BAUD_HALF_PERIOD);
|
||||
|
||||
const fn baud_period_slow_percentage(percent: i128) -> SimDuration {
|
||||
let v = BAUD_PERIOD.as_attos();
|
||||
let v = v.strict_add_signed(v as i128 * percent / 100);
|
||||
SimDuration::from_attos(v)
|
||||
}
|
||||
|
||||
/// 3% slow BAUD_PERIOD
|
||||
const BAUD_PERIOD_SLOW: SimDuration = baud_period_slow_percentage(3);
|
||||
/// 3% fast BAUD_PERIOD
|
||||
const BAUD_PERIOD_FAST: SimDuration = baud_period_slow_percentage(-3);
|
||||
|
||||
const _: () = {
|
||||
assert!(BAUD_HALF_PERIOD.as_attos() < BAUD_PERIOD_FAST.as_attos());
|
||||
assert!(BAUD_PERIOD_FAST.as_attos() < BAUD_PERIOD.as_attos());
|
||||
assert!(BAUD_PERIOD.as_attos() < BAUD_PERIOD_SLOW.as_attos());
|
||||
};
|
||||
|
||||
fn clock_input_properties() -> PhantomConst<peripherals::ClockInputProperties> {
|
||||
peripherals::ClockInput::new(CLOCK_FREQUENCY).properties
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn reference_baud_rate_clk_gen() {
|
||||
#[hdl]
|
||||
let reference_baud_rate_clk: Clock = m.output();
|
||||
m.extern_module_simulation_fn(
|
||||
reference_baud_rate_clk,
|
||||
async |reference_baud_rate_clk, mut sim| {
|
||||
loop {
|
||||
sim.write_clock(reference_baud_rate_clk, false).await;
|
||||
sim.advance_time(BAUD_HALF_PERIOD).await;
|
||||
sim.write_clock(reference_baud_rate_clk, true).await;
|
||||
sim.advance_time(BAUD_HALF_PERIOD).await;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn sim_receiver() {
|
||||
#[hdl]
|
||||
let tx: Bool = m.input();
|
||||
#[hdl]
|
||||
let text_out: SimOnly<String> = m.output();
|
||||
m.extern_module_simulation_fn((tx, text_out), async |(tx, text_out), mut sim| {
|
||||
let mut text = SimOnlyValue::new(String::new());
|
||||
sim.write(text_out, &text).await;
|
||||
loop {
|
||||
// wait for the starting edge of the start bit
|
||||
while sim.read_bool(tx).await {
|
||||
sim.wait_for_changes([tx], None).await;
|
||||
}
|
||||
// wait till the middle of the start bit
|
||||
sim.advance_time(BAUD_HALF_PERIOD).await;
|
||||
let mut byte = 0u8;
|
||||
for i in 0..u8::BITS {
|
||||
// wait till the middle of the next data bit
|
||||
sim.advance_time(BAUD_PERIOD).await;
|
||||
if sim.read_bool(tx).await {
|
||||
byte |= 1 << i;
|
||||
}
|
||||
}
|
||||
assert!(byte.is_ascii());
|
||||
text.push(byte as char);
|
||||
sim.write(text_out, &text).await;
|
||||
// wait till the middle of the stop bit
|
||||
sim.advance_time(BAUD_PERIOD).await;
|
||||
// we're in the middle of the stop bit, the stop bit is a one so we can just loop and wait for
|
||||
// the next transition to zero which is the next start bit.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
fn transmitter_and_uart_clock_gen() {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let tx: Bool = m.output();
|
||||
#[hdl]
|
||||
let input_byte: ReadyValid<UInt<8>> = m.input();
|
||||
#[hdl]
|
||||
let reference_baud_rate_clk: Clock = m.output();
|
||||
#[hdl]
|
||||
let text_out: SimOnly<String> = m.output();
|
||||
|
||||
#[hdl]
|
||||
let sim_receiver = instance(sim_receiver());
|
||||
connect(sim_receiver.tx, tx);
|
||||
connect(text_out, sim_receiver.text_out);
|
||||
|
||||
#[hdl]
|
||||
let reference_baud_rate_clk_gen = instance(reference_baud_rate_clk_gen());
|
||||
connect(
|
||||
reference_baud_rate_clk,
|
||||
reference_baud_rate_clk_gen.reference_baud_rate_clk,
|
||||
);
|
||||
|
||||
#[hdl]
|
||||
let transmitter = instance(transmitter());
|
||||
connect(transmitter.cd, cd);
|
||||
connect(transmitter.input_byte, input_byte);
|
||||
connect(tx, transmitter.tx);
|
||||
#[hdl]
|
||||
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
|
||||
connect(clock_gen.cd, cd);
|
||||
connect(transmitter.tick, clock_gen.tick);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[hdl]
|
||||
fn test_transmitter_and_uart_clock_gen() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
let m = transmitter_and_uart_clock_gen();
|
||||
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);
|
||||
let text = "Hi!\n";
|
||||
let mut bytes = text.as_bytes().iter();
|
||||
for cycle in 0..1000 {
|
||||
let input_byte = match bytes.clone().next() {
|
||||
Some(b) if cycle > 10 =>
|
||||
{
|
||||
#[hdl(sim)]
|
||||
HdlSome(b)
|
||||
}
|
||||
_ =>
|
||||
{
|
||||
#[hdl(sim)]
|
||||
HdlNone()
|
||||
}
|
||||
};
|
||||
sim.write(sim.io().input_byte.data, &input_byte);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
if sim.read_bool(sim.io().input_byte.ready) {
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(_) = input_byte {
|
||||
bytes.next();
|
||||
}
|
||||
}
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, false);
|
||||
}
|
||||
assert_eq!(sim.read(sim.io().text_out).as_str(), text);
|
||||
assert!(bytes.as_slice().is_empty());
|
||||
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
if vcd != include_str!("expected/transmitter_and_uart_clock_gen.vcd") {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn receiver_no_queue_test_cases(text: Interned<str>) {
|
||||
#[hdl]
|
||||
let rx: Bool = m.output();
|
||||
#[hdl]
|
||||
let cur_byte: UInt<8> = m.output();
|
||||
m.extern_module_simulation_fn(
|
||||
(rx, cur_byte, text),
|
||||
async |(rx, cur_byte, text), mut sim| {
|
||||
// ensure the receiver can properly handle slightly fast or slow baud rates
|
||||
let baud_periods = [
|
||||
BAUD_PERIOD_FAST,
|
||||
BAUD_PERIOD_FAST,
|
||||
BAUD_PERIOD_FAST,
|
||||
BAUD_PERIOD_FAST,
|
||||
BAUD_PERIOD,
|
||||
BAUD_PERIOD,
|
||||
BAUD_PERIOD,
|
||||
BAUD_PERIOD,
|
||||
BAUD_PERIOD_SLOW,
|
||||
BAUD_PERIOD_SLOW,
|
||||
BAUD_PERIOD_SLOW,
|
||||
BAUD_PERIOD_SLOW,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
.cycle();
|
||||
sim.write(rx, true).await;
|
||||
sim.write(cur_byte, 0u8).await;
|
||||
// allow time for reset and stuff
|
||||
sim.advance_time(SimDuration::from_micros(100)).await;
|
||||
for (byte, baud_period) in text.bytes().cycle().zip(baud_periods) {
|
||||
sim.write(cur_byte, byte).await;
|
||||
sim.write(rx, false).await; // start bit
|
||||
sim.advance_time(baud_period).await;
|
||||
for i in 0..u8::BITS {
|
||||
sim.write(rx, byte & (1 << i) != 0).await; // data bit
|
||||
sim.advance_time(baud_period).await;
|
||||
}
|
||||
sim.write(rx, true).await; // stop bit
|
||||
sim.advance_time(baud_period).await;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
fn receiver_no_queue_and_uart_clock_gen(text: Interned<str>) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let rx: Bool = m.output();
|
||||
#[hdl]
|
||||
let output_byte: HdlOption<UInt<8>> = m.output();
|
||||
#[hdl]
|
||||
let cur_byte: UInt<8> = m.output();
|
||||
#[hdl]
|
||||
let reference_baud_rate_clk: Clock = m.output();
|
||||
|
||||
#[hdl]
|
||||
let test_cases = instance(receiver_no_queue_test_cases(text));
|
||||
connect(rx, test_cases.rx);
|
||||
connect(cur_byte, test_cases.cur_byte);
|
||||
|
||||
#[hdl]
|
||||
let reference_baud_rate_clk_gen = instance(reference_baud_rate_clk_gen());
|
||||
connect(
|
||||
reference_baud_rate_clk,
|
||||
reference_baud_rate_clk_gen.reference_baud_rate_clk,
|
||||
);
|
||||
|
||||
// only need one stage of synchronization in the simulator
|
||||
#[hdl]
|
||||
let rx_synchronized = reg_builder().clock_domain(cd).reset(true);
|
||||
|
||||
connect(rx_synchronized, rx);
|
||||
|
||||
#[hdl]
|
||||
let receiver = instance(receiver_no_queue());
|
||||
connect(receiver.cd, cd);
|
||||
connect(receiver.rx_synchronized, rx_synchronized);
|
||||
connect(output_byte, receiver.output_byte);
|
||||
#[hdl]
|
||||
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
|
||||
connect(clock_gen.cd, cd);
|
||||
connect(receiver.tick, clock_gen.tick);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[hdl]
|
||||
fn test_receiver_no_queue_and_uart_clock_gen() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
let text = "Testing 123, testing, testing.\n".intern();
|
||||
let m = receiver_no_queue_and_uart_clock_gen(text);
|
||||
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);
|
||||
let mut received_text = String::new();
|
||||
for _cycle in 0..10000 {
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, false);
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(byte) = sim.read(sim.io().output_byte) {
|
||||
let byte = byte.as_int();
|
||||
let expected_byte = sim.read(sim.io().cur_byte).as_int();
|
||||
assert_eq!(
|
||||
byte, expected_byte,
|
||||
"byte={:#x} {:?}, expected_byte={:#x} {:?}",
|
||||
byte, byte as char, expected_byte, expected_byte as char,
|
||||
);
|
||||
assert!(byte.is_ascii());
|
||||
received_text.push(byte as char);
|
||||
}
|
||||
}
|
||||
println!("{received_text:?}");
|
||||
assert!(received_text.len() >= text.len());
|
||||
let mut expected_text = text.repeat(received_text.len().div_ceil(text.len()));
|
||||
expected_text.truncate(received_text.len());
|
||||
assert_eq!(received_text, expected_text);
|
||||
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
if vcd != include_str!("expected/receiver_no_queue_and_uart_clock_gen.vcd") {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn receiver_test_cases() {
|
||||
#[hdl]
|
||||
let clk: Clock = m.input();
|
||||
#[hdl]
|
||||
let rx: Bool = m.output();
|
||||
#[hdl]
|
||||
let cur_byte: UInt<8> = m.output();
|
||||
#[hdl]
|
||||
let ready: Bool = m.output();
|
||||
#[hdl]
|
||||
let text: SimOnly<String> = m.input();
|
||||
m.register_clock_for_past(clk);
|
||||
m.extern_module_simulation_fn(
|
||||
(clk, rx, cur_byte, ready, text),
|
||||
async |(clk, rx, cur_byte, ready, text), mut sim| {
|
||||
sim.write(rx, true).await;
|
||||
sim.write(ready, false).await;
|
||||
loop {
|
||||
sim.write(cur_byte, 0u8).await;
|
||||
sim.wait_for_clock_edge(clk).await;
|
||||
sim.write(ready, true).await;
|
||||
let text = loop {
|
||||
sim.wait_for_clock_edge(clk).await;
|
||||
let text = sim.read_past(text, clk).await;
|
||||
if text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
break text;
|
||||
};
|
||||
sim.write(ready, false).await;
|
||||
sim.wait_for_clock_edge(clk).await;
|
||||
for byte in text.bytes() {
|
||||
sim.write(cur_byte, byte).await;
|
||||
sim.write(rx, false).await; // start bit
|
||||
sim.advance_time(BAUD_PERIOD).await;
|
||||
for i in 0..u8::BITS {
|
||||
sim.write(rx, byte & (1 << i) != 0).await; // data bit
|
||||
sim.advance_time(BAUD_PERIOD).await;
|
||||
}
|
||||
sim.write(rx, true).await; // stop bit
|
||||
sim.advance_time(BAUD_PERIOD).await;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
fn receiver_and_uart_clock_gen(queue_capacity: NonZeroUsize) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let rx: Bool = m.output();
|
||||
#[hdl]
|
||||
let reference_baud_rate_clk: Clock = m.output();
|
||||
#[hdl]
|
||||
let cur_byte: UInt<8> = m.output();
|
||||
#[hdl]
|
||||
let ready: Bool = m.output();
|
||||
#[hdl]
|
||||
let text: SimOnly<String> = m.input();
|
||||
#[hdl]
|
||||
let output_byte: ReadyValid<UInt<8>> = m.output();
|
||||
#[hdl]
|
||||
let queue_status: ReceiverQueueStatus = m.output();
|
||||
|
||||
#[hdl]
|
||||
let test_cases = instance(receiver_test_cases());
|
||||
connect(test_cases.clk, cd.clk);
|
||||
connect(rx, test_cases.rx);
|
||||
connect(cur_byte, test_cases.cur_byte);
|
||||
connect(ready, test_cases.ready);
|
||||
connect(test_cases.text, text);
|
||||
|
||||
#[hdl]
|
||||
let reference_baud_rate_clk_gen = instance(reference_baud_rate_clk_gen());
|
||||
connect(
|
||||
reference_baud_rate_clk,
|
||||
reference_baud_rate_clk_gen.reference_baud_rate_clk,
|
||||
);
|
||||
|
||||
// only need one stage of synchronization in the simulator
|
||||
#[hdl]
|
||||
let rx_synchronized = reg_builder().clock_domain(cd).reset(true);
|
||||
|
||||
connect(rx_synchronized, rx);
|
||||
|
||||
#[hdl]
|
||||
let receiver = instance(receiver(queue_capacity));
|
||||
connect(receiver.cd, cd);
|
||||
connect(receiver.rx_synchronized, rx_synchronized);
|
||||
connect(output_byte, receiver.output_byte);
|
||||
connect(queue_status, receiver.queue_status);
|
||||
#[hdl]
|
||||
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
|
||||
connect(clock_gen.cd, cd);
|
||||
connect(receiver.tick, clock_gen.tick);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[hdl]
|
||||
fn test_receiver_and_uart_clock_gen() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
const QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(4).expect("not zero");
|
||||
let m = receiver_and_uart_clock_gen(QUEUE_SIZE);
|
||||
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);
|
||||
let empty_text = SimOnlyValue::new(String::new());
|
||||
sim.write(sim.io().text, &empty_text);
|
||||
sim.write_bool(sim.io().output_byte.ready, false);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, false);
|
||||
const TEST_TEXT: &str = "Test\n";
|
||||
const { assert!(TEST_TEXT.len() > QUEUE_SIZE.get()) }
|
||||
for bytes_before_dequeue in 0..=TEST_TEXT.len() {
|
||||
println!("\nbytes_before_dequeue={bytes_before_dequeue}");
|
||||
for _cycle in 0..10 {
|
||||
if sim.read_bool(sim.io().ready) {
|
||||
break;
|
||||
}
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
}
|
||||
assert!(sim.read_bool(sim.io().ready));
|
||||
let text = SimOnlyValue::new(TEST_TEXT[..bytes_before_dequeue].to_string());
|
||||
dbg!(&text);
|
||||
sim.write(sim.io().text, &text);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write(sim.io().text, &empty_text);
|
||||
for _cycle in 0..2000 {
|
||||
if sim.read_bool(sim.io().ready) {
|
||||
break;
|
||||
}
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
}
|
||||
assert!(sim.read_bool(sim.io().ready));
|
||||
for _cycle in 0..10 {
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
}
|
||||
dbg!(bytes_before_dequeue);
|
||||
#[hdl(sim)]
|
||||
match dbg!(sim.read(sim.io().queue_status)) {
|
||||
ReceiverQueueStatus::QueueEmpty => {
|
||||
assert_eq!(bytes_before_dequeue, 0);
|
||||
}
|
||||
ReceiverQueueStatus::QueueNotEmpty => {
|
||||
assert!(bytes_before_dequeue > 0 && bytes_before_dequeue < QUEUE_SIZE.get() - 1);
|
||||
}
|
||||
ReceiverQueueStatus::QueueAlmostFull => {
|
||||
assert_eq!(bytes_before_dequeue, QUEUE_SIZE.get() - 1);
|
||||
}
|
||||
ReceiverQueueStatus::QueueFull => {
|
||||
assert_eq!(bytes_before_dequeue, QUEUE_SIZE.get());
|
||||
}
|
||||
ReceiverQueueStatus::QueueOverflowed => {
|
||||
assert!(bytes_before_dequeue > QUEUE_SIZE.get());
|
||||
}
|
||||
ReceiverQueueStatus::Unknown => unreachable!(),
|
||||
}
|
||||
let expected_text = &TEST_TEXT[..bytes_before_dequeue.min(QUEUE_SIZE.get())];
|
||||
dbg!(expected_text);
|
||||
let mut received_text = String::new();
|
||||
for expected_byte in expected_text.bytes() {
|
||||
sim.write_bool(sim.io().output_byte.ready, true);
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(byte) = sim.read(sim.io().output_byte.data) {
|
||||
let byte = byte.as_int();
|
||||
assert_eq!(
|
||||
byte, expected_byte,
|
||||
"byte={:#x} {:?}, expected_byte={:#x} {:?}",
|
||||
byte, byte as char, expected_byte, expected_byte as char,
|
||||
);
|
||||
assert!(byte.is_ascii());
|
||||
received_text.push(byte as char);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
}
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(_) = sim.read(sim.io().output_byte.data) {
|
||||
panic!("queue should be empty now");
|
||||
}
|
||||
sim.write_bool(sim.io().output_byte.ready, false);
|
||||
}
|
||||
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
if vcd != include_str!("expected/receiver_and_uart_clock_gen.vcd") {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
fn simple_uart_loopback(
|
||||
config: PhantomConst<MemoryInterfaceConfig>,
|
||||
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
|
||||
baud_rate: f64,
|
||||
receiver_queue_size: NonZeroUsize,
|
||||
) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
|
||||
m.input(MemoryInterface[config]);
|
||||
#[hdl]
|
||||
let tx: Bool = m.output();
|
||||
#[hdl]
|
||||
let inner = instance(simple_uart(
|
||||
config,
|
||||
clock_input_properties,
|
||||
baud_rate,
|
||||
receiver_queue_size,
|
||||
));
|
||||
connect(inner.cd, cd);
|
||||
connect(inner.memory_interface, memory_interface);
|
||||
connect(tx, inner.uart.tx);
|
||||
connect(inner.uart.rx, inner.uart.tx);
|
||||
}
|
||||
|
||||
const SIMPLE_UART_MEMORY_INTERFACE_CONFIG: MemoryInterfaceConfig =
|
||||
simple_uart_memory_interface_config(FETCH_BLOCK_ID_WIDTH, Wrapping(0));
|
||||
|
||||
struct MemoryOperationRunner<Sim: BorrowMut<Simulation<T>>, T: BundleType> {
|
||||
sim: Sim,
|
||||
clk: Expr<Clock>,
|
||||
memory_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
|
||||
next_op_id: u64,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
const BUS_WIDTH_IN_BYTES: usize = SIMPLE_UART_MEMORY_INTERFACE_CONFIG.bus_width_in_bytes();
|
||||
|
||||
impl<Sim: BorrowMut<Simulation<T>>, T: BundleType> MemoryOperationRunner<Sim, T> {
|
||||
#[hdl]
|
||||
fn run_memory_operation(
|
||||
&mut self,
|
||||
kind: impl ToSimValue<Type = MemoryOperationKind>,
|
||||
addr: u64,
|
||||
write_data: [u8; BUS_WIDTH_IN_BYTES],
|
||||
rw_mask: [bool; BUS_WIDTH_IN_BYTES],
|
||||
timeout_cycles: usize,
|
||||
) -> Result<[u8; BUS_WIDTH_IN_BYTES], SimValue<MemoryOperationErrorKind>> {
|
||||
let kind = kind.into_sim_value();
|
||||
assert_eq!(addr % BUS_WIDTH_IN_BYTES as u64, 0);
|
||||
let config = self.memory_interface.ty().config;
|
||||
let op_id = self.next_op_id.cast_to(UInt[FETCH_BLOCK_ID_WIDTH]);
|
||||
self.next_op_id += 1;
|
||||
println!(
|
||||
"started: MemoryOperationStart {{\n \
|
||||
kind: {kind:?},\n \
|
||||
addr: {addr:#x},\n \
|
||||
write_data: {write_data:?},\n \
|
||||
rw_mask: {rw_mask:?},\n \
|
||||
op_id: {op_id},\n\
|
||||
}}"
|
||||
);
|
||||
let start_value = #[hdl(sim)]
|
||||
MemoryOperationStart::<_> {
|
||||
kind,
|
||||
addr,
|
||||
write_data: &write_data[..],
|
||||
rw_mask: &rw_mask[..],
|
||||
op_id: op_id,
|
||||
config,
|
||||
};
|
||||
#[hdl]
|
||||
let MemoryInterface::<_> {
|
||||
start,
|
||||
finish,
|
||||
next_op_ids,
|
||||
config: _,
|
||||
} = self.memory_interface;
|
||||
let sim = self.sim.borrow_mut();
|
||||
let check_io = |sim: &mut Simulation<T>,
|
||||
expected_start_ready: bool,
|
||||
expected_finish_data_is_some: bool,
|
||||
expected_next_op_ids: &[_]| {
|
||||
assert_eq!(sim.read_bool(start.ready), expected_start_ready);
|
||||
let finish_data = sim.read(finish.data);
|
||||
let finish_data_is_some = #[hdl(sim)]
|
||||
if let HdlSome(_) = &finish_data {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
assert_eq!(
|
||||
finish_data_is_some, expected_finish_data_is_some,
|
||||
"finish.data: {finish_data:?}"
|
||||
);
|
||||
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(next_op_ids) = sim.read(next_op_ids) {
|
||||
let next_op_ids: Vec<_> = ArrayVec::elements_sim_ref(&next_op_ids)
|
||||
.iter()
|
||||
.map(|v| {
|
||||
v.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
|
||||
.as_int()
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(next_op_ids, expected_next_op_ids);
|
||||
} else {
|
||||
panic!("next_op_ids should be HdlSome");
|
||||
}
|
||||
};
|
||||
check_io(sim, true, false, &[]);
|
||||
sim.write(
|
||||
start.data,
|
||||
#[hdl(sim)]
|
||||
(start.ty().data).HdlSome(&start_value),
|
||||
);
|
||||
sim.write(finish.ready, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(self.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(self.clk, false);
|
||||
sim.write(
|
||||
start.data,
|
||||
#[hdl(sim)]
|
||||
(start.ty().data).HdlNone(),
|
||||
);
|
||||
check_io(
|
||||
sim,
|
||||
false,
|
||||
false,
|
||||
&[start_value
|
||||
.op_id
|
||||
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
|
||||
.as_int()],
|
||||
);
|
||||
for _ in 0..timeout_cycles {
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(self.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(self.clk, false);
|
||||
#[hdl(sim)]
|
||||
if let HdlSome(finish_data) = sim.read(finish.data) {
|
||||
#[hdl(sim)]
|
||||
let MemoryOperationFinish::<_> {
|
||||
kind,
|
||||
read_data,
|
||||
config: _,
|
||||
} = finish_data;
|
||||
assert_eq!(read_data.len(), BUS_WIDTH_IN_BYTES);
|
||||
let read_data = std::array::from_fn(|i| read_data[i].as_int());
|
||||
println!(
|
||||
"finished: MemoryOperationFinish {{\n \
|
||||
kind: {kind:?},\n \
|
||||
read_data: {read_data:?},\n\
|
||||
}}"
|
||||
);
|
||||
check_io(
|
||||
sim,
|
||||
false,
|
||||
true,
|
||||
&[start_value
|
||||
.op_id
|
||||
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
|
||||
.as_int()],
|
||||
);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(self.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(self.clk, false);
|
||||
sim.write(finish.ready, false);
|
||||
check_io(sim, true, false, &[]);
|
||||
return #[hdl(sim)]
|
||||
match kind {
|
||||
MemoryOperationFinishKind::Success(kind) => {
|
||||
assert_eq!(
|
||||
SimValue::bits(&kind),
|
||||
SimValue::bits(&start_value.kind),
|
||||
"finish_data.kind={kind:?}",
|
||||
);
|
||||
Ok(read_data)
|
||||
}
|
||||
MemoryOperationFinishKind::Error(error) => Err(error),
|
||||
};
|
||||
}
|
||||
check_io(
|
||||
sim,
|
||||
false,
|
||||
false,
|
||||
&[start_value
|
||||
.op_id
|
||||
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
|
||||
.as_int()],
|
||||
);
|
||||
}
|
||||
panic!("memory operation took too long");
|
||||
}
|
||||
#[hdl]
|
||||
fn rw_bytes(
|
||||
&mut self,
|
||||
start_addr: u64,
|
||||
is_read: bool,
|
||||
bytes: &mut [u8],
|
||||
timeout_cycles: usize,
|
||||
) -> Result<(), SimValue<MemoryOperationErrorKind>> {
|
||||
let offset_in_chunk = start_addr as usize % BUS_WIDTH_IN_BYTES;
|
||||
let masked_addr = start_addr - offset_in_chunk as u64;
|
||||
assert!(
|
||||
offset_in_chunk
|
||||
.checked_add(bytes.len())
|
||||
.is_some_and(|v| v <= BUS_WIDTH_IN_BYTES)
|
||||
);
|
||||
let bytes_range = offset_in_chunk..(offset_in_chunk + bytes.len());
|
||||
let mut write_data = [0u8; BUS_WIDTH_IN_BYTES];
|
||||
let mut rw_mask = [false; BUS_WIDTH_IN_BYTES];
|
||||
rw_mask[bytes_range.clone()].fill(true);
|
||||
if !is_read {
|
||||
write_data[bytes_range.clone()].copy_from_slice(bytes);
|
||||
}
|
||||
let read_bytes = self.run_memory_operation(
|
||||
if is_read {
|
||||
#[hdl(sim)]
|
||||
MemoryOperationKind.Read()
|
||||
} else {
|
||||
#[hdl(sim)]
|
||||
MemoryOperationKind.Write()
|
||||
},
|
||||
masked_addr,
|
||||
write_data,
|
||||
rw_mask,
|
||||
timeout_cycles,
|
||||
)?;
|
||||
if is_read {
|
||||
bytes.copy_from_slice(&read_bytes[bytes_range]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[hdl]
|
||||
fn read_bytes<const N: usize>(
|
||||
&mut self,
|
||||
start_addr: u64,
|
||||
timeout_cycles: usize,
|
||||
) -> Result<[u8; N], SimValue<MemoryOperationErrorKind>> {
|
||||
let mut retval = [0; _];
|
||||
self.rw_bytes(start_addr, true, &mut retval, timeout_cycles)?;
|
||||
Ok(retval)
|
||||
}
|
||||
#[hdl]
|
||||
fn write_bytes<const N: usize>(
|
||||
&mut self,
|
||||
start_addr: u64,
|
||||
mut bytes: [u8; N],
|
||||
timeout_cycles: usize,
|
||||
) -> Result<(), SimValue<MemoryOperationErrorKind>> {
|
||||
self.rw_bytes(start_addr, false, &mut bytes, timeout_cycles)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[hdl]
|
||||
fn test_simple_uart() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
const RECEIVER_QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(16).expect("not zero");
|
||||
let m = simple_uart_loopback(
|
||||
PhantomConst::new_sized(SIMPLE_UART_MEMORY_INTERFACE_CONFIG),
|
||||
clock_input_properties(),
|
||||
BAUD_RATE,
|
||||
RECEIVER_QUEUE_SIZE,
|
||||
);
|
||||
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(
|
||||
sim.io().memory_interface.start.data,
|
||||
sim.io().ty().memory_interface.start.data.HdlNone(),
|
||||
);
|
||||
sim.write(sim.io().memory_interface.finish.ready, false);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, true);
|
||||
sim.advance_time(CLOCK_HALF_PERIOD);
|
||||
sim.write_clock(sim.io().cd.clk, false);
|
||||
sim.write_reset(sim.io().cd.rst, false);
|
||||
let mut mem_op_runner = MemoryOperationRunner {
|
||||
clk: sim.io().cd.clk,
|
||||
memory_interface: sim.io().memory_interface,
|
||||
next_op_id: 0,
|
||||
_phantom: PhantomData,
|
||||
sim,
|
||||
};
|
||||
|
||||
let empty_status = ReceiverQueueStatus::sim_as_u8(
|
||||
#[hdl(sim)]
|
||||
ReceiverQueueStatus.QueueEmpty(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
mem_op_runner
|
||||
.read_bytes(SIMPLE_UART_STATUS_OFFSET, 1)
|
||||
.unwrap(),
|
||||
[empty_status],
|
||||
);
|
||||
|
||||
mem_op_runner
|
||||
.write_bytes(SIMPLE_UART_STATUS_OFFSET, [0], 1)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
mem_op_runner
|
||||
.read_bytes(SIMPLE_UART_RECEIVE_OFFSET, 1)
|
||||
.unwrap(),
|
||||
[0],
|
||||
);
|
||||
|
||||
const { assert!(SIMPLE_UART_RECEIVE_OFFSET + 1 == SIMPLE_UART_STATUS_OFFSET) };
|
||||
|
||||
assert_eq!(
|
||||
mem_op_runner
|
||||
.read_bytes(SIMPLE_UART_RECEIVE_OFFSET, 1)
|
||||
.unwrap(),
|
||||
[0, empty_status],
|
||||
);
|
||||
|
||||
for i in 0..2 * BUS_WIDTH_IN_BYTES as u64 {
|
||||
mem_op_runner
|
||||
.read_bytes::<1>(SIMPLE_UART_USED_SIZE.get() + i, 1)
|
||||
.unwrap_err();
|
||||
mem_op_runner
|
||||
.write_bytes(SIMPLE_UART_USED_SIZE.get() + i, [0], 1)
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
const TEST_TEXT: &str = "Test test test test\n";
|
||||
|
||||
const DELAY: usize = 2;
|
||||
|
||||
const {
|
||||
assert!(
|
||||
TEST_TEXT.len() > RECEIVER_QUEUE_SIZE.get() + DELAY,
|
||||
"TEST_TEXT must be long enough to make the queue overflow when accounting for DELAY"
|
||||
)
|
||||
};
|
||||
|
||||
for (i, b) in TEST_TEXT.bytes().enumerate() {
|
||||
let current_count = i + 1;
|
||||
mem_op_runner
|
||||
.write_bytes(SIMPLE_UART_TRANSMIT_OFFSET, [b], 300)
|
||||
.unwrap();
|
||||
let expected_status = ReceiverQueueStatus::for_queue_len_as_u8(
|
||||
current_count.saturating_sub(DELAY),
|
||||
RECEIVER_QUEUE_SIZE,
|
||||
);
|
||||
assert_eq!(
|
||||
mem_op_runner
|
||||
.read_bytes(SIMPLE_UART_STATUS_OFFSET, 1)
|
||||
.unwrap(),
|
||||
[expected_status],
|
||||
);
|
||||
}
|
||||
|
||||
for (i, mut expected_byte) in TEST_TEXT.bytes().enumerate() {
|
||||
let read = mem_op_runner
|
||||
.read_bytes(SIMPLE_UART_RECEIVE_OFFSET, 1)
|
||||
.unwrap();
|
||||
let current_count = if i == 0 {
|
||||
RECEIVER_QUEUE_SIZE.get() + 1 // initial read starts with overflow
|
||||
} else {
|
||||
RECEIVER_QUEUE_SIZE.get().saturating_sub(i)
|
||||
};
|
||||
if current_count == 0 {
|
||||
expected_byte = 0;
|
||||
}
|
||||
let expected_status =
|
||||
ReceiverQueueStatus::for_queue_len_as_u8(current_count, RECEIVER_QUEUE_SIZE);
|
||||
assert_eq!(read, [expected_byte, expected_status]);
|
||||
}
|
||||
|
||||
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
if vcd != include_str!("expected/simple_uart.vcd") {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue