diff --git a/Cargo.lock b/Cargo.lock index 18e40cd..5fd33a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fayalite" version = "0.2.0" -source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d0229fbcfb11666b5abf7ae487ffe335866f9326" +source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571" dependencies = [ "bitvec", "blake3", @@ -300,7 +300,7 @@ dependencies = [ [[package]] name = "fayalite-proc-macros" version = "0.2.0" -source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d0229fbcfb11666b5abf7ae487ffe335866f9326" +source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571" dependencies = [ "fayalite-proc-macros-impl", ] @@ -308,7 +308,7 @@ dependencies = [ [[package]] name = "fayalite-proc-macros-impl" version = "0.2.0" -source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d0229fbcfb11666b5abf7ae487ffe335866f9326" +source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571" dependencies = [ "base16ct", "num-bigint", @@ -323,7 +323,7 @@ dependencies = [ [[package]] name = "fayalite-visit-gen" version = "0.2.0" -source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#d0229fbcfb11666b5abf7ae487ffe335866f9326" +source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#3939ce2360138c75c53972368069c8f882111571" dependencies = [ "indexmap", "prettyplease", diff --git a/crates/cpu/src/config.rs b/crates/cpu/src/config.rs index 2b76d62..e144e32 100644 --- a/crates/cpu/src/config.rs +++ b/crates/cpu/src/config.rs @@ -1,18 +1,21 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::instruction::{PRegNum, UnitKind, UnitNum}; +use crate::{ + instruction::{PRegNum, UnitNum, CONST_ZERO_UNIT_NUM}, + unit::UnitKind, +}; use fayalite::prelude::*; #[derive(Clone, Eq, PartialEq, Hash, Debug)] #[non_exhaustive] pub struct CpuConfig { - pub units: Vec, + pub unit_kinds: Vec, pub out_reg_num_width: usize, } impl CpuConfig { pub fn unit_num_width(&self) -> usize { - UInt::range(0..self.units.len()).width() + UInt::range((CONST_ZERO_UNIT_NUM + 1)..self.unit_kinds.len()).width() } pub fn unit_num(&self) -> UnitNum { UnitNum[self.unit_num_width()] diff --git a/crates/cpu/src/instruction.rs b/crates/cpu/src/instruction.rs index 00617ea..66adb27 100644 --- a/crates/cpu/src/instruction.rs +++ b/crates/cpu/src/instruction.rs @@ -1,102 +1,11 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::config::CpuConfig; +use crate::unit::UnitMOp; use fayalite::prelude::*; use std::marker::PhantomData; pub mod power_isa; -macro_rules! all_units { - ( - #[hdl_unit_kind = $HdlUnitKind:ident] - #[unit_kind = $UnitKind:ident] - #[hdl] - $(#[$enum_meta:meta])* - $vis:vis enum $UnitMOpEnum:ident<$RegWidth:ident: Size> { - $( - $(#[$variant_meta:meta])* - $Unit:ident($Op:ty), - )* - } - ) => { - $(#[$enum_meta])* - #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] - $vis enum $UnitKind { - $( - $(#[$variant_meta])* - $Unit, - )* - } - - impl ToExpr for $UnitKind { - type Type = $HdlUnitKind; - - fn to_expr(&self) -> Expr { - match self { - $($UnitKind::$Unit => $HdlUnitKind.$Unit(),)* - } - } - } - - #[hdl] - $(#[$enum_meta])* - $vis enum $HdlUnitKind { - $( - $(#[$variant_meta])* - $Unit, - )* - } - - #[hdl] - $(#[$enum_meta])* - $vis enum $UnitMOpEnum<$RegWidth: Size> { - $( - $(#[$variant_meta])* - $Unit($Op), - )* - } - - impl<$RegWidth: Size> $UnitMOpEnum<$RegWidth> { - #[hdl] - $vis fn kind(expr: impl ToExpr) -> Expr<$HdlUnitKind> { - #[hdl] - let unit_kind = wire(); - #[hdl] - match expr { - $($UnitMOpEnum::<$RegWidth>::$Unit(_) => connect(unit_kind, $HdlUnitKind.$Unit()),)* - } - unit_kind - } - } - - impl CpuConfig { - #[hdl] - pub fn available_units_for_kind(&self, unit_kind: impl ToExpr) -> Expr> { - #[hdl] - let available_units_for_kind = wire(Array[Bool][self.units.len()]); - #[hdl] - match unit_kind { - $($HdlUnitKind::$Unit => for (index, &unit) in self.units.iter().enumerate() { - connect(available_units_for_kind[index], unit == $UnitKind::$Unit); - })* - } - available_units_for_kind - } - } - }; -} - -all_units! { - #[hdl_unit_kind = HdlUnitKind] - #[unit_kind = UnitKind] - #[hdl] - pub enum UnitMOp { - AluBranch(AluBranchMOp), - L2RegisterFile(L2RegisterFileMOp), - LoadStore(LoadStoreMOp), - } -} - #[hdl] pub enum OutputIntegerMode { Full64, @@ -239,11 +148,14 @@ pub enum LoadStoreMOp { } #[hdl] -/// there may be more than one unit of a given kind, so UnitNum is not the same as UnitKind +/// there may be more than one unit of a given kind, so UnitNum is not the same as UnitKind. +/// zero is used for built-in constants, such as the zero register pub struct UnitNum { pub value: UIntType, } +pub const CONST_ZERO_UNIT_NUM: usize = 0; + #[hdl] pub struct UnitOutRegNum { pub value: UIntType, diff --git a/crates/cpu/src/lib.rs b/crates/cpu/src/lib.rs index 7263b44..bae3720 100644 --- a/crates/cpu/src/lib.rs +++ b/crates/cpu/src/lib.rs @@ -2,5 +2,7 @@ // See Notices.txt for copyright information pub mod config; pub mod instruction; +pub mod reg_alloc; pub mod register; +pub mod unit; pub mod util; diff --git a/crates/cpu/src/reg_alloc.rs b/crates/cpu/src/reg_alloc.rs new file mode 100644 index 0000000..c47d6ff --- /dev/null +++ b/crates/cpu/src/reg_alloc.rs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::config::CpuConfig; +use fayalite::prelude::*; + +pub mod unit_free_regs_tracker; + +#[hdl_module] +pub fn reg_alloc(config: CpuConfig) { + todo!() +} diff --git a/crates/cpu/src/reg_alloc/unit_free_regs_tracker.rs b/crates/cpu/src/reg_alloc/unit_free_regs_tracker.rs new file mode 100644 index 0000000..69f8d30 --- /dev/null +++ b/crates/cpu/src/reg_alloc/unit_free_regs_tracker.rs @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::util::tree_reduce::tree_reduce; +use fayalite::{prelude::*, util::ready_valid::ReadyValid}; +use std::{num::NonZeroUsize, ops::Range}; + +#[hdl_module] +pub fn unit_free_regs_tracker( + free_at_once: NonZeroUsize, + alloc_at_once: NonZeroUsize, + reg_num_width: usize, +) { + #[hdl] + let cd: ClockDomain = m.input(); + #[hdl] + let free_in: Array> = + m.input(Array[ReadyValid[UInt[reg_num_width]]][free_at_once.get()]); + #[hdl] + let alloc_out: Array> = + m.input(Array[ReadyValid[UInt[reg_num_width]]][alloc_at_once.get()]); + assert!( + reg_num_width <= isize::MAX.ilog2() as usize, + "too many registers" + ); + let reg_count = 1usize << reg_num_width; + #[hdl] + let allocated_reg = reg_builder().clock_domain(cd).reset(vec![false; reg_count]); + for free_index in 0..free_at_once.get() { + connect(free_in[free_index].ready, true); + #[hdl] + if let HdlSome(free_num) = ReadyValid::firing_data(free_in[free_index]) { + connect(allocated_reg[free_num], false); + } + } + struct Summary { + range: Range, + count: Expr, + count_overflowed: Expr, + alloc_nums: Expr>, + } + let count_ty = UInt::range_inclusive(0..=alloc_at_once.get()); + let Some(Summary { + range: _, + count, + count_overflowed, + alloc_nums, + }) = tree_reduce( + (0..reg_count).map(|index| Summary { + range: index..index + 1, + count: allocated_reg[index] + .cast_to_static::>() + .cast_to(count_ty), + count_overflowed: false.to_expr(), + alloc_nums: repeat(UInt[0].zero(), alloc_at_once.get()), + }), + |l, r| { + let range = l.range.start..r.range.end; + #[hdl] + let reduced_count = wire(count_ty); + connect_any(reduced_count, l.count + r.count); + #[hdl] + let reduced_count_overflowed = wire(); + connect( + reduced_count_overflowed, + (l.count + r.count).cmp_ne(reduced_count) | l.count_overflowed | r.count_overflowed, + ); + #[hdl] + let reduced_alloc_nums = wire( + Array[UInt[Expr::ty(l.alloc_nums).element().width() + 1]][alloc_at_once.get()], + ); + for alloc_index in 0..alloc_at_once.get() { + #[hdl] + if l.count_overflowed | l.count.cmp_ge(alloc_at_once) { + connect(reduced_alloc_nums[alloc_index], l.alloc_nums[alloc_index]); + } else { + connect(reduced_alloc_nums[alloc_index], r.alloc_nums[alloc_index]); + } + } + Summary { + range, + count: reduced_count, + count_overflowed: reduced_count_overflowed, + alloc_nums: reduced_alloc_nums, + } + }, + ) + else { + unreachable!("reg_count is known to be non-zero"); + }; + for alloc_index in 0..alloc_at_once.get() { + #[hdl] + if let HdlSome(alloc_num) = ReadyValid::firing_data(alloc_out[alloc_index]) { + connect(allocated_reg[alloc_num], true); + } + #[hdl] + if count_overflowed | count.cmp_ge(alloc_index) { + connect( + alloc_out[alloc_index].data, + HdlSome(alloc_nums[alloc_index]), + ); + } else { + connect( + alloc_out[alloc_index].data, + HdlOption[UInt[reg_num_width]].HdlNone(), + ); + } + } +} + +// TODO: add formal proof of unit_free_regs_tracker diff --git a/crates/cpu/src/unit.rs b/crates/cpu/src/unit.rs new file mode 100644 index 0000000..ad1e581 --- /dev/null +++ b/crates/cpu/src/unit.rs @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + config::CpuConfig, + instruction::{AluBranchMOp, L2RegisterFileMOp, LoadStoreMOp, PRegNum}, + register::PRegValue, +}; +use fayalite::{ + bundle::{Bundle, BundleType}, + intern::{Intern, Interned}, + prelude::*, + util::ready_valid::ReadyValid, +}; + +macro_rules! all_units { + ( + #[hdl_unit_kind = $HdlUnitKind:ident] + #[unit_kind = $UnitKind:ident] + #[hdl] + $(#[$enum_meta:meta])* + $vis:vis enum $UnitMOpEnum:ident<$RegWidth:ident: Size> { + $( + $(#[$variant_meta:meta])* + $Unit:ident($Op:ty), + )* + } + ) => { + $(#[$enum_meta])* + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] + $vis enum $UnitKind { + $( + $(#[$variant_meta])* + $Unit, + )* + } + + impl ToExpr for $UnitKind { + type Type = $HdlUnitKind; + + fn to_expr(&self) -> Expr { + match self { + $($UnitKind::$Unit => $HdlUnitKind.$Unit(),)* + } + } + } + + #[hdl] + $(#[$enum_meta])* + $vis enum $HdlUnitKind { + $( + $(#[$variant_meta])* + $Unit, + )* + } + + #[hdl] + $(#[$enum_meta])* + $vis enum $UnitMOpEnum<$RegWidth: Size> { + $( + $(#[$variant_meta])* + $Unit($Op), + )* + } + + impl<$RegWidth: Size> $UnitMOpEnum<$RegWidth> { + #[hdl] + $vis fn kind(expr: impl ToExpr) -> Expr<$HdlUnitKind> { + #[hdl] + let unit_kind = wire(); + #[hdl] + match expr { + $($UnitMOpEnum::<$RegWidth>::$Unit(_) => connect(unit_kind, $HdlUnitKind.$Unit()),)* + } + unit_kind + } + } + + impl CpuConfig { + #[hdl] + pub fn available_units_for_kind(&self, unit_kind: impl ToExpr) -> Expr> { + #[hdl] + let available_units_for_kind = wire(Array[Bool][self.unit_kinds.len()]); + #[hdl] + match unit_kind { + $($HdlUnitKind::$Unit => for (index, &unit_kind) in self.unit_kinds.iter().enumerate() { + connect(available_units_for_kind[index], unit_kind == $UnitKind::$Unit); + })* + } + available_units_for_kind + } + } + }; +} + +all_units! { + #[hdl_unit_kind = HdlUnitKind] + #[unit_kind = UnitKind] + #[hdl] + pub enum UnitMOp { + AluBranch(AluBranchMOp), + L2RegisterFile(L2RegisterFileMOp), + LoadStore(LoadStoreMOp), + } +} + +#[hdl] +pub struct UnitResultCompleted { + pub value: PRegValue, + pub extra_out: ExtraOut, +} + +#[hdl] +pub struct TrapData { + // TODO +} + +#[hdl] +pub enum UnitResult { + Completed(UnitResultCompleted), + Trap(TrapData), +} + +#[hdl] +pub struct UnitOutput { + pub which: PRegNum, + pub result: UnitResult, +} + +#[hdl] +pub struct UnitCancelInput { + pub which: PRegNum, +} + +pub trait UnitTrait: + 'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId +{ + type Type: BundleType; + type ExtraOut: Type; + type MOp: Type; + + fn ty(&self) -> Self::Type; + fn extra_out_ty(&self) -> Self::ExtraOut; + fn mop_ty(&self) -> Self::MOp; + + fn unit_kind(&self) -> UnitKind; + + fn make_module(&self) -> Interned>; + + // TODO: add other inputs + fn cancel_input( + &self, + this: Expr, + ) -> Expr>>; + fn output( + &self, + this: Expr, + ) -> Expr>>; + + fn to_dyn(&self) -> DynUnit; +} + +type DynUnitTrait = dyn UnitTrait; + +impl fayalite::intern::InternedCompare for DynUnitTrait { + type InternedCompareKey = fayalite::intern::PtrEqWithTypeId; + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + Self::get_ptr_eq_with_type_id(this) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct DynUnit { + ty: Bundle, + extra_out_ty: CanonicalType, + mop_ty: CanonicalType, + unit_kind: UnitKind, + unit: Interned, +} + +impl UnitTrait for DynUnit { + type Type = Bundle; + type ExtraOut = CanonicalType; + type MOp = CanonicalType; + + fn ty(&self) -> Self::Type { + self.ty + } + + fn extra_out_ty(&self) -> Self::ExtraOut { + self.extra_out_ty + } + + fn mop_ty(&self) -> Self::MOp { + self.mop_ty + } + + fn unit_kind(&self) -> UnitKind { + self.unit_kind + } + + fn make_module(&self) -> Interned> { + self.unit.make_module() + } + + fn cancel_input( + &self, + this: Expr, + ) -> Expr>> { + self.unit.cancel_input(this) + } + + fn output( + &self, + this: Expr, + ) -> Expr>> { + self.unit.output(this) + } + + fn to_dyn(&self) -> DynUnit { + *self + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct DynUnitWrapper(pub T); + +impl UnitTrait for DynUnitWrapper { + type Type = Bundle; + type ExtraOut = CanonicalType; + type MOp = CanonicalType; + + fn ty(&self) -> Self::Type { + Bundle::from_canonical(self.0.ty().canonical()) + } + + fn extra_out_ty(&self) -> Self::ExtraOut { + self.0.extra_out_ty().canonical() + } + + fn mop_ty(&self) -> Self::MOp { + self.0.mop_ty().canonical() + } + + fn unit_kind(&self) -> UnitKind { + self.0.unit_kind() + } + + fn make_module(&self) -> Interned> { + self.0.make_module().canonical().intern_sized() + } + + fn cancel_input( + &self, + this: Expr, + ) -> Expr>> { + self.0.cancel_input(Expr::from_bundle(this)) + } + + fn output( + &self, + this: Expr, + ) -> Expr>> { + Expr::from_bundle(Expr::as_bundle(self.0.output(Expr::from_bundle(this)))) + } + + fn to_dyn(&self) -> DynUnit { + let unit = self.intern(); + DynUnit { + ty: unit.ty(), + extra_out_ty: unit.extra_out_ty(), + mop_ty: unit.mop_ty(), + unit_kind: unit.unit_kind(), + unit: Interned::cast_unchecked(unit, |v: &Self| -> &DynUnitTrait { v }), + } + } +} diff --git a/crates/cpu/src/util.rs b/crates/cpu/src/util.rs index 60713b5..9d18b31 100644 --- a/crates/cpu/src/util.rs +++ b/crates/cpu/src/util.rs @@ -1,5 +1,8 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information + +pub mod tree_reduce; + pub(crate) const fn range_u32_len(range: &std::ops::Range) -> usize { let retval = range.end.saturating_sub(range.start); assert!(retval as usize as u32 != retval, "len overflowed"); diff --git a/crates/cpu/src/util/tree_reduce.rs b/crates/cpu/src/util/tree_reduce.rs new file mode 100644 index 0000000..c8d12f7 --- /dev/null +++ b/crates/cpu/src/util/tree_reduce.rs @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum TreeReduceOp { + Input, + Reduce, +} + +#[derive(Copy, Clone, Debug)] +struct Entry { + start: usize, + depth: u32, +} + +#[derive(Clone, Debug)] +pub struct TreeReduceOps { + len: usize, + stack: Vec, +} + +impl TreeReduceOps { + pub fn new(len: usize) -> Self { + TreeReduceOps { + len, + stack: Vec::new(), + } + } +} + +impl Iterator for TreeReduceOps { + type Item = TreeReduceOp; + fn next(&mut self) -> Option { + match *self.stack { + [] if self.len != 0 => { + self.stack.push(Entry { start: 0, depth: 0 }); + Some(TreeReduceOp::Input) + } + [.., ref mut second_last, last] if second_last.depth == last.depth => { + second_last.depth += 1; + self.stack.pop(); + Some(TreeReduceOp::Reduce) + } + [.., last] if self.len - last.start > 1 << last.depth => { + let start = last.start + (1 << last.depth); + self.stack.push(Entry { start, depth: 0 }); + Some(TreeReduceOp::Input) + } + [.., ref mut second_last, _] => { + second_last.depth += 1; + self.stack.pop(); + Some(TreeReduceOp::Reduce) + } + _ => None, + } + } +} + +#[track_caller] +pub fn tree_reduce_with_state( + iter: impl IntoIterator, + state: &mut S, + mut input: impl FnMut(&mut S, I) -> R, + mut reduce: impl FnMut(&mut S, R, R) -> R, +) -> Option { + let mut stack = Vec::new(); + let mut iter = iter.into_iter(); + for op in TreeReduceOps::new(iter.len()) { + match op { + TreeReduceOp::Input => stack.push(input( + state, + iter.next().expect("inconsistent iterator len() and next()"), + )), + TreeReduceOp::Reduce => { + let Some(r) = stack.pop() else { + unreachable!(); + }; + let Some(l) = stack.pop() else { + unreachable!(); + }; + stack.push(reduce(state, l, r)); + } + } + } + stack.pop() +} + +pub fn tree_reduce( + iter: impl IntoIterator, + mut reduce: impl FnMut(T, T) -> T, +) -> Option { + tree_reduce_with_state(iter, &mut (), |_, v| v, move |_, l, r| reduce(l, r)) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ops::Range; + + fn recursive_tree_reduce(range: Range, ops: &mut Vec) { + if range.len() == 1 { + ops.push(TreeReduceOp::Input); + return; + } + if range.is_empty() { + return; + } + let pow2_len = range.len().next_power_of_two(); + let split = range.start + pow2_len / 2; + recursive_tree_reduce(range.start..split, ops); + recursive_tree_reduce(split..range.end, ops); + ops.push(TreeReduceOp::Reduce); + } + + #[test] + fn test_tree_reduce() { + const EXPECTED: &'static [&'static [TreeReduceOp]] = { + use TreeReduceOp::{Input as I, Reduce as R}; + &[ + &[], + &[I], + &[I, I, R], + &[I, I, R, I, R], + &[I, I, R, I, I, R, R], + &[I, I, R, I, I, R, R, I, R], + &[I, I, R, I, I, R, R, I, I, R, R], + &[I, I, R, I, I, R, R, I, I, R, I, R, R], + &[I, I, R, I, I, R, R, I, I, R, I, I, R, R, R], + ] + }; + for len in 0..64 { + let mut expected = vec![]; + recursive_tree_reduce(0..len, &mut expected); + if let Some(&expected2) = EXPECTED.get(len) { + assert_eq!(*expected, *expected2, "len={len}"); + } + assert_eq!( + TreeReduceOps::new(len).collect::>(), + expected, + "len={len}" + ); + let seq: Vec<_> = (0..len).collect(); + assert_eq!( + seq, + tree_reduce(seq.iter().map(|&v| vec![v]), |mut l, r| { + l.extend_from_slice(&r); + l + }) + .unwrap_or_default() + ); + } + } +}