forked from libre-chip/cpu
175 lines
5.7 KiB
Rust
175 lines
5.7 KiB
Rust
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
// See Notices.txt for copyright information
|
|
|
|
use cpu::main_memory_and_io::simple_uart::{transmitter, uart_clock_gen};
|
|
use fayalite::{
|
|
prelude::*,
|
|
sim::vcd::VcdWriterDecls,
|
|
util::{RcWriter, ready_valid::ReadyValid},
|
|
};
|
|
|
|
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);
|
|
|
|
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!();
|
|
}
|
|
}
|
|
|
|
// TODO: test receiver()
|
|
// TODO: test simple_uart()
|