forked from libre-chip/cpu
add memory_interface_adapter_no_split
This commit is contained in:
parent
a15367c37e
commit
5bdc71acc3
6 changed files with 238654 additions and 25 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -358,9 +358,9 @@ pub fn receiver(queue_capacity: NonZeroUsize) {
|
||||||
pub const SIMPLE_UART_RECEIVE_OFFSET: u64 = 0;
|
pub const SIMPLE_UART_RECEIVE_OFFSET: u64 = 0;
|
||||||
pub const SIMPLE_UART_TRANSMIT_OFFSET: u64 = 0;
|
pub const SIMPLE_UART_TRANSMIT_OFFSET: u64 = 0;
|
||||||
pub const SIMPLE_UART_STATUS_OFFSET: u64 = 1;
|
pub const SIMPLE_UART_STATUS_OFFSET: u64 = 1;
|
||||||
pub const SIMPLE_UART_SIZE: NonZeroU64 =
|
pub const SIMPLE_UART_LOG2_BUS_WIDTH: u8 = 1;
|
||||||
NonZeroU64::new(1 << SIMPLE_UART_LOG2_SIZE).expect("known to be non-zero");
|
pub const SIMPLE_UART_USED_SIZE: NonZeroU64 = NonZeroU64::new(2).expect("known non-zero");
|
||||||
pub const SIMPLE_UART_LOG2_SIZE: u8 = 1;
|
pub const SIMPLE_UART_ADDRESS_SIZE: NonZeroU64 = NonZeroU64::new(1 << 6).expect("known non-zero");
|
||||||
|
|
||||||
#[hdl(no_static)]
|
#[hdl(no_static)]
|
||||||
struct Operation<C: PhantomConstGet<MemoryInterfaceConfig>> {
|
struct Operation<C: PhantomConstGet<MemoryInterfaceConfig>> {
|
||||||
|
|
@ -373,16 +373,18 @@ pub const fn simple_uart_memory_interface_config(
|
||||||
start_address: Wrapping<u64>,
|
start_address: Wrapping<u64>,
|
||||||
) -> MemoryInterfaceConfig {
|
) -> MemoryInterfaceConfig {
|
||||||
assert!(
|
assert!(
|
||||||
start_address.0 % SIMPLE_UART_SIZE.get() == 0,
|
start_address
|
||||||
|
.0
|
||||||
|
.is_multiple_of(SIMPLE_UART_ADDRESS_SIZE.get()),
|
||||||
"start_address must be properly aligned"
|
"start_address must be properly aligned"
|
||||||
);
|
);
|
||||||
MemoryInterfaceConfig {
|
MemoryInterfaceConfig {
|
||||||
log2_bus_width_in_bytes: SIMPLE_UART_LOG2_SIZE,
|
log2_bus_width_in_bytes: SIMPLE_UART_LOG2_BUS_WIDTH,
|
||||||
queue_capacity: const { NonZeroUsize::new(1).unwrap() },
|
queue_capacity: const { NonZeroUsize::new(1).unwrap() },
|
||||||
op_id_width,
|
op_id_width,
|
||||||
address_range: AddressRange::Limited {
|
address_range: AddressRange::Limited {
|
||||||
start: start_address,
|
start: start_address,
|
||||||
size: SIMPLE_UART_SIZE,
|
size: SIMPLE_UART_ADDRESS_SIZE,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
236660
crates/cpu/tests/expected/memory_interface_adapter_no_split.vcd
generated
Normal file
236660
crates/cpu/tests/expected/memory_interface_adapter_no_split.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
22
crates/cpu/tests/expected/simple_uart.vcd
generated
22
crates/cpu/tests/expected/simple_uart.vcd
generated
|
|
@ -549,7 +549,7 @@ b0 YK<TM
|
||||||
0dM>i/
|
0dM>i/
|
||||||
0f><7V
|
0f><7V
|
||||||
b0 #+y/d
|
b0 #+y/d
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) k$)j$
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) k$)j$
|
||||||
1z'<O{
|
1z'<O{
|
||||||
sHdlNone\x20(0) kC4Z@
|
sHdlNone\x20(0) kC4Z@
|
||||||
sSuccess\x20(0) DSj6>
|
sSuccess\x20(0) DSj6>
|
||||||
|
|
@ -557,13 +557,13 @@ sRead\x20(0) i8%Hy
|
||||||
sGeneric\x20(0) }u<b|
|
sGeneric\x20(0) }u<b|
|
||||||
b0 [i|C-
|
b0 [i|C-
|
||||||
b0 NB*xP
|
b0 NB*xP
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) b|P(G
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) b|P(G
|
||||||
0p\ULz
|
0p\ULz
|
||||||
sHdlSome\x20(1) $x:NB
|
sHdlSome\x20(1) $x:NB
|
||||||
b0 E*{c-
|
b0 E*{c-
|
||||||
0:C'8U
|
0:C'8U
|
||||||
sPhantomConst(\"0..=1\") E=NM+
|
sPhantomConst(\"0..=1\") E=NM+
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) eQf|o
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) eQf|o
|
||||||
1-x}z)
|
1-x}z)
|
||||||
0*K&(X
|
0*K&(X
|
||||||
1N12hA
|
1N12hA
|
||||||
|
|
@ -575,7 +575,7 @@ b0 ^'LkA
|
||||||
0LMy1=
|
0LMy1=
|
||||||
0YG=uN
|
0YG=uN
|
||||||
b0 ),#g]
|
b0 ),#g]
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) pukV=
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) pukV=
|
||||||
1\u*{A
|
1\u*{A
|
||||||
sHdlNone\x20(0) ax>!d
|
sHdlNone\x20(0) ax>!d
|
||||||
sSuccess\x20(0) $TH!~
|
sSuccess\x20(0) $TH!~
|
||||||
|
|
@ -583,13 +583,13 @@ sRead\x20(0) @nru
|
||||||
sGeneric\x20(0) =rNet
|
sGeneric\x20(0) =rNet
|
||||||
b0 _aF6z
|
b0 _aF6z
|
||||||
b0 fBY~q
|
b0 fBY~q
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) 3Cr%:
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) 3Cr%:
|
||||||
0jP_rY
|
0jP_rY
|
||||||
sHdlSome\x20(1) fl1G|
|
sHdlSome\x20(1) fl1G|
|
||||||
b0 >-gg}
|
b0 >-gg}
|
||||||
0d2Gl|
|
0d2Gl|
|
||||||
sPhantomConst(\"0..=1\") |@PWl
|
sPhantomConst(\"0..=1\") |@PWl
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) 7A<7@
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) 7A<7@
|
||||||
1?:DT^
|
1?:DT^
|
||||||
1*V%!4
|
1*V%!4
|
||||||
01-L[%
|
01-L[%
|
||||||
|
|
@ -730,14 +730,14 @@ b0 &]$B;
|
||||||
0N-YM;
|
0N-YM;
|
||||||
0R,uS'
|
0R,uS'
|
||||||
b0 GEq-0
|
b0 GEq-0
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) ,;U@A
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) ,;U@A
|
||||||
sHdlNone\x20(0) 0?MV,
|
sHdlNone\x20(0) 0?MV,
|
||||||
sSuccess\x20(0) D>VoK
|
sSuccess\x20(0) D>VoK
|
||||||
sRead\x20(0) d8ku7
|
sRead\x20(0) d8ku7
|
||||||
sGeneric\x20(0) Y[<1d
|
sGeneric\x20(0) Y[<1d
|
||||||
b0 .^N`/
|
b0 .^N`/
|
||||||
b0 'ThQe
|
b0 'ThQe
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) 8pZSz
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) 8pZSz
|
||||||
1'LW/7
|
1'LW/7
|
||||||
1(+=/^
|
1(+=/^
|
||||||
b0 Whx|K
|
b0 Whx|K
|
||||||
|
|
@ -752,7 +752,7 @@ b0 qwy9i
|
||||||
044.En
|
044.En
|
||||||
0Pq(.9
|
0Pq(.9
|
||||||
b0 4zG]Q
|
b0 4zG]Q
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) MTVBb
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) MTVBb
|
||||||
1IifsN
|
1IifsN
|
||||||
sHdlNone\x20(0) EA1Ra
|
sHdlNone\x20(0) EA1Ra
|
||||||
sSuccess\x20(0) [<sgF
|
sSuccess\x20(0) [<sgF
|
||||||
|
|
@ -760,13 +760,13 @@ sRead\x20(0) j.}nI
|
||||||
sGeneric\x20(0) z\\(W
|
sGeneric\x20(0) z\\(W
|
||||||
b0 Z*eyX
|
b0 Z*eyX
|
||||||
b0 MRBv>
|
b0 MRBv>
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) {[zRy
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) {[zRy
|
||||||
0q2OoF
|
0q2OoF
|
||||||
sHdlSome\x20(1) d#Of8
|
sHdlSome\x20(1) d#Of8
|
||||||
b0 J4y0s
|
b0 J4y0s
|
||||||
0vC#Sj
|
0vC#Sj
|
||||||
sPhantomConst(\"0..=1\") ye/Gl
|
sPhantomConst(\"0..=1\") ye/Gl
|
||||||
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) bH=hU
|
sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":64}}}) bH=hU
|
||||||
1"QAdC
|
1"QAdC
|
||||||
1K%|4s
|
1K%|4s
|
||||||
$end
|
$end
|
||||||
|
|
|
||||||
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!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,8 +6,8 @@ use cpu::{
|
||||||
MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind, MemoryOperationFinish,
|
MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind, MemoryOperationFinish,
|
||||||
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
|
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
|
||||||
simple_uart::{
|
simple_uart::{
|
||||||
ReceiverQueueStatus, SIMPLE_UART_RECEIVE_OFFSET, SIMPLE_UART_SIZE,
|
ReceiverQueueStatus, SIMPLE_UART_RECEIVE_OFFSET, SIMPLE_UART_STATUS_OFFSET,
|
||||||
SIMPLE_UART_STATUS_OFFSET, SIMPLE_UART_TRANSMIT_OFFSET, receiver, receiver_no_queue,
|
SIMPLE_UART_TRANSMIT_OFFSET, SIMPLE_UART_USED_SIZE, receiver, receiver_no_queue,
|
||||||
simple_uart, simple_uart_memory_interface_config, transmitter, uart_clock_gen,
|
simple_uart, simple_uart_memory_interface_config, transmitter, uart_clock_gen,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -920,10 +920,10 @@ fn test_simple_uart() {
|
||||||
|
|
||||||
for i in 0..2 * BUS_WIDTH_IN_BYTES as u64 {
|
for i in 0..2 * BUS_WIDTH_IN_BYTES as u64 {
|
||||||
mem_op_runner
|
mem_op_runner
|
||||||
.read_bytes::<1>(SIMPLE_UART_SIZE.get() + i, 1)
|
.read_bytes::<1>(SIMPLE_UART_USED_SIZE.get() + i, 1)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
mem_op_runner
|
mem_op_runner
|
||||||
.write_bytes(SIMPLE_UART_SIZE.get() + i, [0], 1)
|
.write_bytes(SIMPLE_UART_USED_SIZE.get() + i, [0], 1)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue