WIP: adding memory_interface_adapter_no_split

This commit is contained in:
Jacob Lifshay 2026-03-10 21:23:47 -07:00
parent 3080ea4ce2
commit 2457116fc0
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
5 changed files with 8841 additions and 13 deletions

File diff suppressed because it is too large Load diff

View file

@ -358,9 +358,9 @@ pub fn receiver(queue_capacity: NonZeroUsize) {
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_SIZE: NonZeroU64 =
NonZeroU64::new(1 << SIMPLE_UART_LOG2_SIZE).expect("known to be non-zero");
pub const SIMPLE_UART_LOG2_SIZE: u8 = 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>> {
@ -373,16 +373,18 @@ pub const fn simple_uart_memory_interface_config(
start_address: Wrapping<u64>,
) -> MemoryInterfaceConfig {
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"
);
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() },
op_id_width,
address_range: AddressRange::Limited {
start: start_address,
size: SIMPLE_UART_SIZE,
size: SIMPLE_UART_ADDRESS_SIZE,
},
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,660 @@
// 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,
};
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;
}
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
{
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) {
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 {
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)]
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;
}
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
{
sim.wait_for_clock_edge(cd.clk).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 {delay}");
for _ in 0..delay {
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;
}
sim.wait_for_clock_edge(cd.clk).await;
};
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| 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] = *contents.as_bytes().get(
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 mut op = Op {
addr,
read_mask: [true; BUS_WIDTH],
};
op.read_mask[chunk.len()..].fill(false);
if sequence.last() != Some(&op) {
sequence.push(op);
}
}
}
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
});
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..1000 {
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!();
}
}

View file

@ -6,8 +6,8 @@ use cpu::{
MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind, MemoryOperationFinish,
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
simple_uart::{
ReceiverQueueStatus, SIMPLE_UART_RECEIVE_OFFSET, SIMPLE_UART_SIZE,
SIMPLE_UART_STATUS_OFFSET, SIMPLE_UART_TRANSMIT_OFFSET, receiver, receiver_no_queue,
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,
},
},
@ -920,10 +920,10 @@ fn test_simple_uart() {
for i in 0..2 * BUS_WIDTH_IN_BYTES as u64 {
mem_op_runner
.read_bytes::<1>(SIMPLE_UART_SIZE.get() + i, 1)
.read_bytes::<1>(SIMPLE_UART_USED_SIZE.get() + i, 1)
.unwrap_err();
mem_op_runner
.write_bytes(SIMPLE_UART_SIZE.get() + i, [0], 1)
.write_bytes(SIMPLE_UART_USED_SIZE.get() + i, [0], 1)
.unwrap_err();
}