From 190e440b3534a32572978511c9257d8fb8044bc0 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 16 Jul 2024 19:46:52 -0700 Subject: [PATCH] WIP adding docs --- README.md | 5 + crates/fayalite-proc-macros/src/lib.rs | 6 + crates/fayalite/src/lib.rs | 263 +++++++++++++++++++++++++ crates/fayalite/src/module.rs | 2 + crates/fayalite/src/ty.rs | 1 + 5 files changed, 277 insertions(+) diff --git a/README.md b/README.md index e69de29..6e14e9f 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,5 @@ +# Fayalite + +Fayalite is a library for designing digital hardware -- a hardware description language (HDL) embedded in the Rust programming language. Fayalite's semantics are based on [FIRRTL] as interpreted by [LLVM CIRCT](https://circt.llvm.org/docs/Dialects/FIRRTL/FIRRTLAnnotations/). + +[FIRRTL]: https://github.com/chipsalliance/firrtl-spec diff --git a/crates/fayalite-proc-macros/src/lib.rs b/crates/fayalite-proc-macros/src/lib.rs index d40fb19..7d0c65d 100644 --- a/crates/fayalite-proc-macros/src/lib.rs +++ b/crates/fayalite-proc-macros/src/lib.rs @@ -1,5 +1,10 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information +//! proc macros for `fayalite` +//! +//! see `fayalite::hdl_module` and `fayalite::ty::Value` for docs + +// intentionally not documented here, see `fayalite::hdl_module` for docs #[proc_macro_attribute] pub fn hdl_module( attr: proc_macro::TokenStream, @@ -11,6 +16,7 @@ pub fn hdl_module( } } +// intentionally not documented here, see `fayalite::ty::Value` for docs #[proc_macro_derive(Value, attributes(hdl))] pub fn value_derive(item: proc_macro::TokenStream) -> proc_macro::TokenStream { match fayalite_proc_macros_impl::value_derive(item.into()) { diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 0108e96..b2e9dc0 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -1,10 +1,273 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information + +// TODO: enable: +// #![warn(missing_docs)] + +#![doc = include_str!("../README.md")] +//! +//! # Organization +//! +//! All Fayalite-based designs are organized as one or more [modules][`module::Module`] +//! -- modules are created by writing a Rust function with the +//! [`#[hdl_module]` attribute][hdl_module]. You can then invoke the function to create a module. +//! You use the implicitly-added [`m: ModuleBuilder`][`module::ModuleBuilder`] variable in that +//! function to add inputs/outputs and other components to that module. +//! +//! ``` +//! # use fayalite::{hdl_module, int::UInt}; +//! # +//! #[hdl_module] +//! pub fn example_module() { +//! #[hdl] +//! let an_input: UInt<10> = m.input(); // create an input that is a 10-bit unsigned integer +//! #[hdl] +//! let some_output: UInt<10> = m.output(); +//! m.connect(some_output, an_input); // assigns the value of `an_input` to `some_output` +//! } +//! ``` + extern crate self as fayalite; #[doc(hidden)] pub use std as __std; +#[doc(inline)] +#[doc(alias = "hdl")] +/// The `#[hdl_module]` attribute is applied to a Rust function so that that function creates +/// a [`Module`][`::fayalite::module::Module`] when called. +/// In the function body it will implicitly create a +/// variable [`m: ModuleBuilder`][`module::ModuleBuilder`]. +/// +/// # Module Kinds +/// +/// There are two different kinds of modules: +/// +/// * Normal modules. These are used for general Fayalite-based code. +/// These use the [`NormalModule`][`module::NormalModule`] tag type. +/// * Extern modules. These are for when you want to use modules written in +/// some other language, such as Verilog. +/// You create an extern module by instead using an `#[hdl_module(extern)]` attribute on your +/// module function. You then create inputs/outputs like for normal modules, then you can set +/// the verilog name and parameters using [`ModuleBuilder`][`module::ModuleBuilder`] methods: +/// +/// * [`verilog_name()`][`module::ModuleBuilder::verilog_name`] +/// * [`parameter_int()`][`module::ModuleBuilder::parameter_int`] +/// * [`parameter_str()`][`module::ModuleBuilder::parameter_str`] +/// * [`parameter_raw_verilog()`][`module::ModuleBuilder::parameter_raw_verilog`] +/// * [`parameter()`][`module::ModuleBuilder::parameter`] +/// +/// These use the [`ExternModule`][`module::ExternModule`] tag type. +/// +/// # Module Function Bodies +/// +/// The `#[hdl_module]` attribute lets you have statements/expressions with `#[hdl]` annotations +/// and `_hdl_` integer literals in the function body: +/// +/// ## `_hdl_` integer literals +/// +/// You can have integer literals with an arbitrary number of bits like so: +/// +/// ``` +/// # #[fayalite::hdl_module] +/// # fn module() { +/// let a = 0x1234_hdl_u14; // a UInt<14> with value 0x1234 +/// let b = 0x7_hdl_i3; // a SInt<3> with value 0x7 +/// let lf = b'\n'_hdl; // a UInt<8> with value b'\n' -- aka. 0x0A +/// let large_a = b'A'_hdl; // a UInt<8> with value b'A' -- aka. 0x41 +/// let n5 = -5_hdl_i4; // a SInt<4> with value -5 +/// let n1 = -1_hdl_i200; // a SInt<200> with value -1 +/// let v = 0xfedcba9876543210_fedcba9876543210_fedcba9876543210_hdl_u192; // a UInt<192> +/// let empty = 0_hdl_u0; // a UInt<0> +/// # } +/// ``` +/// +/// ## `#[hdl] let` statements +/// +/// ### Inputs/Outputs +/// +/// ``` +/// # use fayalite::{hdl_module, int::UInt, array::Array}; +/// # #[hdl_module] +/// # fn module() { +/// #[hdl] +/// let my_input: UInt<10> = m.input(); +/// #[hdl] +/// let my_output: Array<[UInt<10>; 3]> = m.output(); +/// # } +/// ``` +/// +/// ### Module Instances +/// +/// module instances are kinda like the hardware equivalent of calling a function, +/// you can create them like so: +/// +/// ``` +/// # use fayalite::{hdl_module, int::UInt, array::Array}; +/// # #[hdl_module] +/// # fn module() { +/// #[hdl] +/// let my_instance = m.instance(some_module()); +/// // now you can use `my_instance`'s inputs/outputs like so: +/// #[hdl] +/// let v: UInt<3> = m.input(); +/// m.connect(my_instance.a, v); +/// #[hdl_module] +/// fn some_module() { +/// #[hdl] +/// let a: UInt<3> = m.input(); +/// // ... +/// } +/// # } +/// ``` +/// +/// ### Registers +/// +/// Registers are memory devices that will change their state only on a clock +/// edge (or when being reset). They retain their state when not connected to. +/// +/// ``` +/// # use fayalite::{hdl_module, int::UInt, array::Array, clock::ClockDomain}; +/// # #[hdl_module] +/// # fn module() { +/// # let v = true; +/// #[hdl] +/// let cd: ClockDomain = m.input(); +/// #[hdl] +/// let my_register: UInt<8> = m.reg_builder().clock_domain(cd).reset(8_hdl_u8); +/// #[hdl] +/// if v { +/// // my_register is only changed when both `v` is set and `cd`'s clock edge occurs. +/// m.connect(my_register, 0x45_hdl_u8); +/// } +/// # } +/// ``` +/// +/// ### Wires +/// +/// Wires are kinda like variables, but unlike registers, +/// they have no memory (they're combinatorial). +/// You must [connect][`module::ModuleBuilder::connect`] to all wires, so they have a defined value. +/// +/// ``` +/// # use fayalite::{hdl_module, int::UInt, array::Array, clock::ClockDomain}; +/// # #[hdl_module] +/// # fn module() { +/// # let v = true; +/// #[hdl] +/// let cd: ClockDomain = m.input(); +/// #[hdl] +/// let my_register: UInt<8> = m.reg_builder().clock_domain(cd).reset(8_hdl_u8); +/// #[hdl] +/// if v { +/// // my_register is only changed when both `v` is set and `cd`'s clock edge occurs. +/// m.connect(my_register, 0x45_hdl_u8); +/// } +/// # } +/// ``` +/// +/// ### Memories +/// +/// Memories are optimized for storing large amounts of data. +/// +/// When you create a memory, you get a [`MemBuilder`][`memory::MemBuilder`], which you +/// can then use to add memory ports, which is how you can read/write the memory. +/// +/// There are several different ways to create a memory: +/// +/// ### using [`ModuleBuilder::memory()`][`module::ModuleBuilder::memory`] +/// +/// This way you have to set the [`depth`][`memory::MemBuilder::depth`] separately. +/// +/// ``` +/// # use fayalite::{hdl_module, int::UInt, clock::ClockDomain}; +/// # #[hdl_module] +/// # fn module() { +/// // first, we need some IO +/// #[hdl] +/// let cd: ClockDomain = m.input(); +/// #[hdl] +/// let read_addr: UInt<8> = m.input(); +/// #[hdl] +/// let read_data: UInt<8> = m.output(); +/// +/// // now create the memory +/// #[hdl] +/// let mut my_memory = m.memory(); +/// my_memory.depth(256); // the memory has 256 elements +/// +/// let read_port = my_memory.new_read_port(); +/// +/// // connect up the read port +/// m.connect_any(read_port.addr, read_addr); +/// m.connect(read_port.en, 1_hdl_u1); +/// m.connect(read_port.clk, cd.clk); +/// m.connect(read_data, read_port.data); +/// +/// // we need more IO for the write port +/// #[hdl] +/// let write_addr: UInt<8> = m.input(); +/// #[hdl] +/// let do_write: UInt<1> = m.input(); +/// #[hdl] +/// let write_data: UInt<8> = m.input(); +/// +/// let write_port = my_memory.new_write_port(); +/// +/// m.connect_any(write_port.addr, write_addr); +/// m.connect(write_port.en, do_write); +/// m.connect(write_port.clk, cd.clk); +/// m.connect(write_port.data, write_port.data); +/// m.connect(write_port.mask, 1_hdl_u1); +/// # } +/// ``` +/// +/// ### using [`ModuleBuilder::memory_array()`][`module::ModuleBuilder::memory_array`] +/// +/// this allows you to specify the memory's underlying array type directly. +/// +/// ``` +/// # use fayalite::{hdl_module, int::UInt, memory::MemBuilder}; +/// # #[hdl_module] +/// # fn module() { +/// #[hdl] +/// let mut my_memory: MemBuilder<[UInt<8>; 256]> = m.memory_array(); +/// +/// let read_port = my_memory.new_read_port(); +/// // ... +/// let write_port = my_memory.new_write_port(); +/// // ... +/// # } +/// ``` +/// +/// ### using [`ModuleBuilder::memory_with_init()`][`module::ModuleBuilder::memory_with_init`] +/// +/// This allows you to deduce the memory's array type from the data used to initialize the memory. +/// +/// ``` +/// # use fayalite::{hdl_module, int::UInt}; +/// # #[hdl_module] +/// # fn module() { +/// # #[hdl] +/// # let read_addr: UInt<2> = m.input(); +/// #[hdl] +/// let mut my_memory = m.memory_with_init( +/// #[hdl] +/// [0x12_hdl_u8, 0x34_hdl_u8, 0x56_hdl_u8, 0x78_hdl_u8], +/// ); +/// +/// let read_port = my_memory.new_read_port(); +/// // note that `read_addr` is `UInt<2>` since the memory only has 4 elements +/// m.connect_any(read_port.addr, read_addr); +/// // ... +/// let write_port = my_memory.new_write_port(); +/// // ... +/// # } +/// ``` +/// +/// # `#[hdl]` expressions/statements: +/// +/// FIXME: finish writing pub use fayalite_proc_macros::hdl_module; pub mod annotations; diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index e503b4c..ac38ee8 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -1108,6 +1108,8 @@ impl ModuleBody { } } +/// The runtime representation of a Fayalite module. The preferred way to create a [`Module`] is by +/// calling a function annotated with the [`#[hdl_module]`][`crate::hdl_module`] attribute. #[derive(PartialEq, Eq, Hash)] pub struct Module where diff --git a/crates/fayalite/src/ty.rs b/crates/fayalite/src/ty.rs index f8e8149..22ae1c3 100644 --- a/crates/fayalite/src/ty.rs +++ b/crates/fayalite/src/ty.rs @@ -28,6 +28,7 @@ use std::{ sync::Arc, }; +#[doc(inline)] pub use fayalite_proc_macros::Value; mod sealed {