forked from libre-chip/cpu
add WIP simple_uart
This commit is contained in:
parent
3fcd1c7338
commit
1d8a948443
4 changed files with 17486 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,
|
||||
|
|
|
|||
157
crates/cpu/src/main_memory_and_io/simple_uart.rs
Normal file
157
crates/cpu/src/main_memory_and_io/simple_uart.rs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
// 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 tx_buf_reg: Bool = reg_builder().clock_domain(cd).reset(true);
|
||||
|
||||
connect(tx, tx_buf_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(tx_buf_reg, v);
|
||||
} else {
|
||||
connect(tx_buf_reg, true);
|
||||
}
|
||||
|
||||
#[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());
|
||||
|
||||
#[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]);
|
||||
}
|
||||
} else 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");
|
||||
}
|
||||
17195
crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd
generated
Normal file
17195
crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
132
crates/cpu/tests/simple_uart.rs
Normal file
132
crates/cpu/tests/simple_uart.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
// 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 = 1e6;
|
||||
const CLOCK_HALF_PERIOD: SimDuration = half_period(CLOCK_FREQUENCY);
|
||||
const BAUD_RATE: f64 = 9600.0;
|
||||
const BAUD_HALF_PERIOD: SimDuration = half_period(BAUD_RATE);
|
||||
|
||||
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]
|
||||
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 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!(bytes.as_slice().is_empty());
|
||||
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
// VCD isn't known to be correct, it's just whatever we happen to generate now
|
||||
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