add sram and main_memory_and_io modules
All checks were successful
/ test (pull_request) Successful in 22m26s
/ test (push) Successful in 23m27s

This commit is contained in:
Jacob Lifshay 2026-03-26 01:24:05 -07:00
parent 5bdc71acc3
commit a1147f0f05
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
3 changed files with 236476 additions and 0 deletions

View file

@ -3,6 +3,7 @@
use crate::{config::CpuConfig, next_pc::FETCH_BLOCK_ID_WIDTH, util::array_vec::ArrayVec};
use fayalite::{
bitvec::{order::Lsb0, view::BitView},
bundle::BundleType,
expr::Valueless,
int::UIntInRangeType,
@ -1404,3 +1405,298 @@ pub fn memory_interface_adapter_no_split<OutputInterfaces: Type + MemoryInterfac
);
}
}
/// get [`MemoryInterfaceConfig`] for [`sram()`]
pub const fn sram_config(suggested_config: MemoryInterfaceConfig) -> MemoryInterfaceConfig {
let MemoryInterfaceConfig {
log2_bus_width_in_bytes,
queue_capacity: _,
op_id_width,
address_range,
} = suggested_config;
let size = address_range
.size()
.expect("must use AddressRange::Limited")
.get();
assert!(size <= usize::MAX as u64, "address_range is too big");
let bus_width_in_bytes = suggested_config.bus_width_in_bytes();
let start = address_range.start().0;
assert!(
start.is_multiple_of(bus_width_in_bytes as u64)
&& size.is_multiple_of(bus_width_in_bytes as u64),
"address_range must start and end at a multiple of bus_width_in_bytes",
);
MemoryInterfaceConfig {
log2_bus_width_in_bytes,
queue_capacity: const { NonZeroUsize::new(3).unwrap() },
op_id_width,
address_range,
}
}
#[hdl_module]
pub fn sram(config: PhantomConst<MemoryInterfaceConfig>, initial_contents: impl AsRef<[u8]>) {
let initial_contents = initial_contents.as_ref();
// sram_config makes sure address_size is properly aligned and not too big
assert_eq!(sram_config(*config.get()), *config.get());
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let input_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
let address_range = config.get().address_range;
let size = address_range
.size()
.expect("already checked by sram_config")
.get();
let bus_width_in_bytes = config.get().bus_width_in_bytes();
let address_range_start = address_range.start().0;
let element_ty = Array[UInt::<8>::new_static()][bus_width_in_bytes];
#[hdl]
let mut mem = memory_array(Array[element_ty][size as usize / bus_width_in_bytes]);
mem.read_latency(0);
mem.write_latency(const { NonZeroUsize::new(1).unwrap() });
mem.read_under_write(ReadUnderWrite::Old);
if !initial_contents.is_empty() {
let mut initial_bits = BitVec::<_, Lsb0>::repeat(
false,
mem.get_array_type()
.expect("already set by memory_array")
.canonical()
.bit_width(),
);
assert!(initial_contents.len() <= size as usize);
let initial_contents_bits = initial_contents.view_bits::<Lsb0>();
initial_bits[..initial_contents_bits.len()].clone_from_bitslice(initial_contents_bits);
mem.initial_value_bit_slice(initial_bits.intern_deref());
}
let read_port = mem.new_read_port();
connect(read_port.addr, read_port.ty().addr.zero());
connect(read_port.clk, cd.clk);
connect(read_port.en, false);
let write_port = mem.new_write_port();
connect(write_port.addr, write_port.ty().addr.zero());
connect(write_port.clk, cd.clk);
connect(write_port.en, false);
connect(write_port.data, repeat(0u8, bus_width_in_bytes));
connect(write_port.mask, repeat(false, bus_width_in_bytes));
#[hdl(no_static)]
struct OpStart<C: PhantomConstGet<MemoryInterfaceConfig>> {
sram_addr: HdlOption<UInt<64>>,
start: MemoryOperationStart<C>,
}
#[hdl]
let before_rw_reg = reg_builder()
.clock_domain(cd)
.reset(HdlOption[OpStart[config]].HdlNone());
#[hdl]
let next_before_rw = wire(before_rw_reg.ty());
#[hdl]
let rw_reg = reg_builder()
.clock_domain(cd)
.reset(HdlOption[OpStart[config]].HdlNone());
#[hdl]
let next_rw = wire(rw_reg.ty());
#[hdl]
let finished_reg = reg_builder()
.clock_domain(cd)
.reset(HdlOption[MemoryOperationFinish[config]].HdlNone());
#[hdl]
let next_finished = wire(finished_reg.ty());
connect(next_before_rw, next_before_rw.ty().HdlNone());
#[hdl]
if let HdlSome(start) = input_interface.start.data {
#[hdl]
let sram_addr_unchecked: UInt<64> = wire();
connect_any(sram_addr_unchecked, start.addr - address_range_start);
#[hdl]
let sram_addr = wire();
#[hdl]
if sram_addr_unchecked.cmp_lt(size) {
connect(sram_addr, HdlSome(sram_addr_unchecked));
} else {
connect(sram_addr, HdlNone());
}
connect(
next_before_rw,
HdlSome(
#[hdl]
OpStart::<_> { sram_addr, start },
),
);
}
connect(next_rw, before_rw_reg);
connect(next_finished, next_finished.ty().HdlNone());
#[hdl]
if let HdlSome(rw) = rw_reg {
#[hdl]
if let HdlSome(sram_addr) = rw.sram_addr {
let addr = sram_addr >> config.get().log2_bus_width_in_bytes as usize;
connect_any(read_port.addr, addr);
connect_any(write_port.addr, addr);
let kind = MemoryOperationFinishKind.Success(rw.start.kind);
#[hdl]
match rw.start.kind {
MemoryOperationKind::Read => {
connect(read_port.en, true);
connect(
next_finished,
HdlSome(
#[hdl]
MemoryOperationFinish::<_> {
kind,
read_data: read_port.data,
config,
},
),
);
}
MemoryOperationKind::Write => {
connect(write_port.en, true);
connect(write_port.data, rw.start.write_data);
connect(write_port.mask, rw.start.rw_mask);
connect(
next_finished,
HdlSome(
#[hdl]
MemoryOperationFinish::<_> {
kind,
read_data: repeat(0u8, bus_width_in_bytes),
config,
},
),
);
}
}
} else {
connect(
next_finished,
HdlSome(
#[hdl]
MemoryOperationFinish::<_> {
kind: MemoryOperationFinishKind.Error(MemoryOperationErrorKind.Generic()),
read_data: repeat(0u8, bus_width_in_bytes),
config,
},
),
);
}
}
connect(input_interface.finish.data, finished_reg);
connect(input_interface.start.ready, input_interface.finish.ready);
#[hdl]
if input_interface.finish.ready {
connect(before_rw_reg, next_before_rw);
connect(rw_reg, next_rw);
connect(finished_reg, next_finished);
}
connect(
input_interface.next_op_ids,
input_interface.ty().next_op_ids.HdlNone(),
);
}
#[hdl_module]
pub fn main_memory_and_io(
config: PhantomConst<MemoryInterfaceConfig>,
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
sram_address_range: AddressRange,
sram_initial_contents: impl AsRef<[u8]>,
uart_start_address: u64,
uart_baud_rate: f64,
uart_receiver_queue_size: NonZeroUsize,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
#[hdl]
let uart: peripherals::Uart = m.output();
let mut sram_config = *config.get();
sram_config.address_range = sram_address_range;
sram_config = self::sram_config(sram_config);
let sram_config = PhantomConst::new_sized(sram_config);
#[hdl]
let sram = instance(sram(sram_config, sram_initial_contents.as_ref()));
connect(sram.cd, cd);
let uart_config = PhantomConst::new_sized(simple_uart::simple_uart_memory_interface_config(
config.get().op_id_width,
Wrapping(uart_start_address),
));
#[hdl]
let simple_uart = instance(simple_uart::simple_uart(
uart_config,
clock_input_properties,
uart_baud_rate,
uart_receiver_queue_size,
));
connect(simple_uart.cd, cd);
connect(uart, simple_uart.uart);
#[hdl(no_static)]
struct OutputInterfaces<
SramConfig: PhantomConstGet<MemoryInterfaceConfig>,
UartConfig: PhantomConstGet<MemoryInterfaceConfig>,
> {
sram: MemoryInterface<SramConfig>,
uart: MemoryInterface<UartConfig>,
}
impl<
SramConfig: Type + PhantomConstGet<MemoryInterfaceConfig>,
UartConfig: Type + PhantomConstGet<MemoryInterfaceConfig>,
> MemoryInterfacesBundle for OutputInterfaces<SramConfig, UartConfig>
{
}
#[hdl]
let adapter = instance(memory_interface_adapter_no_split(
config,
OutputInterfaces[sram_config][uart_config],
));
connect(adapter.cd, cd);
connect(adapter.input_interface, memory_interface);
connect(sram.input_interface, adapter.output_interfaces.sram);
connect(simple_uart.memory_interface, adapter.output_interfaces.uart);
}

File diff suppressed because it is too large Load diff

View 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!();
}
}