Skip to main content

bitloom_macro/
lib.rs

1//! Proc macros expand only to `bitloom_builder` / prelude paths (AD-6). Never depend on rhdl-hir.
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{DeriveInput, ItemFn, parse_macro_input};
6
7/// Marks a struct as an RHDL module shell for Story 1.1.
8/// Generates `Elaboratable` that records directed ports via the builder session.
9#[proc_macro_attribute]
10pub fn module(_attr: TokenStream, item: TokenStream) -> TokenStream {
11    let input = parse_macro_input!(item as DeriveInput);
12    let name = &input.ident;
13    let vis = &input.vis;
14
15    let syn::Data::Struct(data) = &input.data else {
16        return syn::Error::new_spanned(&input, "rhdl::module only supports structs")
17            .to_compile_error()
18            .into();
19    };
20
21    let mod_name = name.to_string();
22    let field_defs = data.fields.iter().map(|f| {
23        let id = f.ident.as_ref().unwrap();
24        let ty = &f.ty;
25        let fvis = &f.vis;
26        quote! { #fvis #id: #ty }
27    });
28
29    let port_stmts = data.fields.iter().map(|field| {
30        let Some(ident) = &field.ident else {
31            return quote! {
32                compile_error!("tuple structs are not supported by rhdl::module");
33            };
34        };
35        let ty = &field.ty;
36        let name_str = ident.to_string();
37        quote! {
38            {
39                type __PortTy = #ty;
40                let (__dir, __gt) = <__PortTy as ::bitloom_prelude::PortField>::describe();
41                match __dir {
42                    ::bitloom_prelude::PortDir::Input => {
43                        __session.add_input(#name_str, __gt, ::bitloom_prelude::Span::default());
44                    }
45                    ::bitloom_prelude::PortDir::Output => {
46                        __session.add_output(#name_str, __gt, ::bitloom_prelude::Span::default());
47                    }
48                }
49            }
50        }
51    });
52
53    TokenStream::from(quote! {
54        #vis struct #name {
55            #(#field_defs),*
56        }
57
58        impl ::bitloom_prelude::Elaboratable for #name {
59            fn elaborate() -> ::core::result::Result<
60                ::bitloom_prelude::FrozenHir,
61                ::bitloom_prelude::Diagnostics,
62            > {
63                let mut __session = ::bitloom_prelude::ElaborateSession::new(#mod_name);
64                __session.begin_module(#mod_name, ::bitloom_prelude::Span::default());
65                #(#port_stmts)*
66                __session.end_module();
67                __session.finish()
68            }
69        }
70    })
71}
72
73/// Marks a hardware process as combinational. Expands to a builder open/close
74/// around the function body (must call session helpers for assigns).
75#[proc_macro_attribute]
76pub fn combinational(_attr: TokenStream, item: TokenStream) -> TokenStream {
77    let input = parse_macro_input!(item as ItemFn);
78    let vis = &input.vis;
79    let sig = &input.sig;
80    let block = &input.block;
81    let attrs = &input.attrs;
82    TokenStream::from(quote! {
83        #(#attrs)*
84        #vis #sig {
85            // Marker retained so unmarked hardware fns are distinguishable.
86            const _: () = ();
87            let __rhdl_process_kind = ::bitloom_prelude::ProcessKindMark::Combinational;
88            let _ = __rhdl_process_kind;
89            #block
90        }
91    })
92}
93
94/// Marks a hardware process as sequential.
95#[proc_macro_attribute]
96pub fn sequential(_attr: TokenStream, item: TokenStream) -> TokenStream {
97    let input = parse_macro_input!(item as ItemFn);
98    let vis = &input.vis;
99    let sig = &input.sig;
100    let block = &input.block;
101    let attrs = &input.attrs;
102    TokenStream::from(quote! {
103        #(#attrs)*
104        #vis #sig {
105            const _: () = ();
106            let __rhdl_process_kind = ::bitloom_prelude::ProcessKindMark::Sequential;
107            let _ = __rhdl_process_kind;
108            #block
109        }
110    })
111}
112
113/// Rejects unmarked hardware process attributes — use `combinational`/`sequential`.
114#[proc_macro_attribute]
115pub fn process(_attr: TokenStream, item: TokenStream) -> TokenStream {
116    let input = parse_macro_input!(item as ItemFn);
117    syn::Error::new_spanned(
118        input.sig.ident,
119        "hardware processes must use #[combinational] or #[sequential]; bare #[process] is forbidden",
120    )
121    .to_compile_error()
122    .into()
123}
124
125/// Marks a handwritten functional model (Story 3.3). Does not enter FrozenHir.
126#[proc_macro_attribute]
127pub fn functional_model(_attr: TokenStream, item: TokenStream) -> TokenStream {
128    host_only_view(item, "FunctionalModel")
129}
130
131/// Handwritten TLM↔pin adapter (FR29). Host-only; never enters FrozenHir / freeze.
132/// Does **not** generate TLM from HIR.
133#[proc_macro_attribute]
134pub fn bridge(_attr: TokenStream, item: TokenStream) -> TokenStream {
135    host_only_view(item, "Bridge")
136}
137
138/// Handwritten untimed / transaction abstraction (FR29). Host-only.
139#[proc_macro_attribute]
140pub fn abstraction(_attr: TokenStream, item: TokenStream) -> TokenStream {
141    host_only_view(item, "Abstraction")
142}
143
144/// Mixed `both` simulation: RTL (`tick`) + handwritten view in one fixture (FR29).
145#[proc_macro_attribute]
146pub fn both(_attr: TokenStream, item: TokenStream) -> TokenStream {
147    host_only_view(item, "Both")
148}
149
150/// Marks a function for optional external HLS (FR35 / AD-25). Never schedules in-process.
151#[proc_macro_attribute]
152pub fn hls(_attr: TokenStream, item: TokenStream) -> TokenStream {
153    let input = parse_macro_input!(item as ItemFn);
154    let vis = &input.vis;
155    let sig = &input.sig;
156    let block = &input.block;
157    let attrs = &input.attrs;
158    TokenStream::from(quote! {
159        #(#attrs)*
160        #vis #sig {
161            const _: () = ();
162            let __rhdl_hls = ::bitloom_prelude::HlsMark;
163            let _ = __rhdl_hls;
164            #block
165        }
166    })
167}
168
169fn host_only_view(item: TokenStream, kind: &str) -> TokenStream {
170    let input = parse_macro_input!(item as DeriveInput);
171    let name = &input.ident;
172    let kind_ident = syn::Ident::new(kind, name.span());
173    TokenStream::from(quote! {
174        #input
175
176        impl ::bitloom_prelude::HostView for #name {
177            const KIND: ::bitloom_prelude::ViewKind = ::bitloom_prelude::ViewKind::#kind_ident;
178        }
179        // Host-only; never participates in freeze/HIR. No HIR→TLM lowering.
180    })
181}
182
183/// Marks the elaboratable top for `cargo rhdl build` (AD-19).
184#[proc_macro_attribute]
185pub fn top(_attr: TokenStream, item: TokenStream) -> TokenStream {
186    let input = parse_macro_input!(item as DeriveInput);
187    let name = &input.ident;
188    TokenStream::from(quote! {
189        #input
190
191        impl #name {
192            /// ABI marker used by the RHDL host/CLI.
193            pub const RHDL_TOP: bool = true;
194        }
195    })
196}