forked from libre-chip/cpu
add main_memory_and_io::simple_uart::{transmitter, uart_clock_gen} and tests
This commit is contained in:
parent
3fcd1c7338
commit
d61475faf4
4 changed files with 20293 additions and 0 deletions
|
|
@ -10,6 +10,8 @@ use crate::{
|
|||
};
|
||||
use fayalite::{prelude::*, util::ready_valid::ReadyValid};
|
||||
|
||||
pub mod simple_uart;
|
||||
|
||||
#[hdl]
|
||||
pub enum MemoryOperationKind {
|
||||
Read,
|
||||
|
|
|
|||
167
crates/cpu/src/main_memory_and_io/simple_uart.rs
Normal file
167
crates/cpu/src/main_memory_and_io/simple_uart.rs
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use crate::config::CpuConfig;
|
||||
use fayalite::{int::UIntInRange, prelude::*, util::ready_valid::ReadyValid};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
pub fn simple_uart(
|
||||
config: PhantomConst<CpuConfig>,
|
||||
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
|
||||
baud_rate: f64,
|
||||
) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[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_syncronized: Bool = wire();
|
||||
annotate(rx_syncronized, DontTouchAnnotation);
|
||||
connect(rx_syncronized, rx_sync_final_reg);
|
||||
|
||||
todo!("add transmitter and receiver");
|
||||
}
|
||||
19949
crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd
generated
Normal file
19949
crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
175
crates/cpu/tests/simple_uart.rs
Normal file
175
crates/cpu/tests/simple_uart.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
// 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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue