formal proof works! also add test_power_isa_add_sim
Some checks failed
/ test (pull_request) Failing after 22m21s
Some checks failed
/ test (pull_request) Failing after 22m21s
This commit is contained in:
parent
1bc59716c5
commit
ef30d325d5
9 changed files with 28747 additions and 211 deletions
|
|
@ -2745,8 +2745,35 @@ const DECODE_FNS: &[(&[&str], DecodeFn)] = &[
|
||||||
|
|
||||||
const MAX_MOPS_PER_INSN: usize = 3;
|
const MAX_MOPS_PER_INSN: usize = 3;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DecodeFilterArgs<'a> {
|
||||||
|
mnemonic: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFilterArgs<'_> {
|
||||||
|
pub fn mnemonic(&self) -> &str {
|
||||||
|
self.mnemonic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DecodeFilter {
|
||||||
|
fn should_include(&mut self, args: &DecodeFilterArgs<'_>) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized + FnMut(&DecodeFilterArgs<'_>) -> bool> DecodeFilter for T {
|
||||||
|
fn should_include(&mut self, args: &DecodeFilterArgs<'_>) -> bool {
|
||||||
|
self(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodeFilter for () {
|
||||||
|
fn should_include(&mut self, _args: &DecodeFilterArgs<'_>) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[hdl_module]
|
#[hdl_module]
|
||||||
pub fn decode_one_insn() {
|
pub fn decode_one_insn(mut filter: impl DecodeFilter) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let output: ArrayVec<TraceAsString<MOp>, ConstUsize<{ MAX_MOPS_PER_INSN }>> = m.output();
|
let output: ArrayVec<TraceAsString<MOp>, ConstUsize<{ MAX_MOPS_PER_INSN }>> = m.output();
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -2816,6 +2843,7 @@ pub fn decode_one_insn() {
|
||||||
let Some(decode_fn) = decode_fns.get(mnemonic) else {
|
let Some(decode_fn) = decode_fns.get(mnemonic) else {
|
||||||
panic!("unhandled mnemonic: {mnemonic:?}");
|
panic!("unhandled mnemonic: {mnemonic:?}");
|
||||||
};
|
};
|
||||||
|
if filter.should_include(&DecodeFilterArgs { mnemonic }) {
|
||||||
decode_fn(&mut DecodeState {
|
decode_fn(&mut DecodeState {
|
||||||
mnemonic,
|
mnemonic,
|
||||||
arguments,
|
arguments,
|
||||||
|
|
@ -2834,6 +2862,7 @@ pub fn decode_one_insn() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DecodeOneInsn for decode_one_insn {
|
impl DecodeOneInsn for decode_one_insn {
|
||||||
type Input = (UInt<32>, HdlOption<UInt<32>>);
|
type Input = (UInt<32>, HdlOption<UInt<32>>);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ impl<T: MOpTrait> MOpInto<T> for T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait MOpLeaf<Target: MOpTrait>: MOpInto<Target> {}
|
||||||
|
|
||||||
pub trait MOpVariantVisitOps {
|
pub trait MOpVariantVisitOps {
|
||||||
type MOp: MOpTrait<
|
type MOp: MOpTrait<
|
||||||
DestReg = <Self::Target as MOpTrait>::DestReg,
|
DestReg = <Self::Target as MOpTrait>::DestReg,
|
||||||
|
|
@ -714,6 +716,35 @@ pub const COMMON_MOP_2_IMM_WIDTH: usize = common_mop_max_imm_size(2);
|
||||||
pub const COMMON_MOP_3_IMM_WIDTH: usize = common_mop_max_imm_size(3);
|
pub const COMMON_MOP_3_IMM_WIDTH: usize = common_mop_max_imm_size(3);
|
||||||
|
|
||||||
macro_rules! common_mop_struct {
|
macro_rules! common_mop_struct {
|
||||||
|
(
|
||||||
|
#[leaf]
|
||||||
|
$(#[debug($($debug:tt)*)])?
|
||||||
|
#[mapped(<$NewDestReg:ident, $NewSrcReg:ident> $mapped_ty:ty)]
|
||||||
|
$(#[$struct_meta:meta])*
|
||||||
|
$vis:vis struct $MOp:ident<$DestReg:ident: $DestRegBound:ident, $SrcReg:ident: $SrcRegBound:ident $(, $Generic:ident: $GenericBound:ident)* $(,)?> {
|
||||||
|
$($body:tt)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
common_mop_struct! {
|
||||||
|
$(#[debug($($debug)*)])?
|
||||||
|
#[mapped(<$NewDestReg, $NewSrcReg> $mapped_ty)]
|
||||||
|
$(#[$struct_meta])*
|
||||||
|
$vis struct $MOp<$DestReg: $DestRegBound, $SrcReg: $SrcRegBound $(, $Generic: $GenericBound)*> {
|
||||||
|
$($body)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
$DestReg: $DestRegBound,
|
||||||
|
$SrcReg: $SrcRegBound,
|
||||||
|
$($Generic: $GenericBound,)*
|
||||||
|
Target: MOpTrait,
|
||||||
|
> MOpLeaf<Target> for $MOp<$DestReg, $SrcReg, $($Generic),*>
|
||||||
|
where
|
||||||
|
Self: MOpInto<Target>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
(
|
(
|
||||||
#[debug($(#[$debug_hdl:ident])? |$debug_value:ident, $debug_f:ident| $debug_block:block $(where $($debug_where:tt)*)?)]
|
#[debug($(#[$debug_hdl:ident])? |$debug_value:ident, $debug_f:ident| $debug_block:block $(where $($debug_where:tt)*)?)]
|
||||||
#[mapped(<$NewDestReg:ident, $SrcReg:ident> $mapped_ty:ty)]
|
#[mapped(<$NewDestReg:ident, $SrcReg:ident> $mapped_ty:ty)]
|
||||||
|
|
@ -1322,6 +1353,7 @@ macro_rules! maybe_write_comma_flag {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let AddSubMOp::<_, _, _> {
|
let AddSubMOp::<_, _, _> {
|
||||||
|
|
@ -1909,6 +1941,7 @@ impl LogicalFlagsMOpImm {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let LogicalFlagsMOp::<_, _> {
|
let LogicalFlagsMOp::<_, _> {
|
||||||
|
|
@ -2073,6 +2106,7 @@ impl<DestReg: Type, SrcReg: Type> LogicalFlagsMOp<DestReg, SrcReg> {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let LogicalMOp::<_, _, _> {
|
let LogicalMOp::<_, _, _> {
|
||||||
|
|
@ -2350,6 +2384,7 @@ impl SimValueDebug for ShiftRotateMOpImm {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let ShiftRotateMOp::<_, _> {
|
let ShiftRotateMOp::<_, _> {
|
||||||
|
|
@ -2465,6 +2500,7 @@ impl CompareMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let CompareMOp::<_, _, _> {
|
let CompareMOp::<_, _, _> {
|
||||||
|
|
@ -2569,6 +2605,7 @@ impl ConditionMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let BranchMOp::<_, _, _> {
|
let BranchMOp::<_, _, _> {
|
||||||
|
|
@ -2736,6 +2773,7 @@ pub enum ReadSpecialMOpImm {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let ReadSpecialMOp::<_, _> {
|
let ReadSpecialMOp::<_, _> {
|
||||||
|
|
@ -2826,6 +2864,7 @@ impl SimValueDebug for L2RegNum {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let ReadL2RegMOp::<_, _> {
|
let ReadL2RegMOp::<_, _> {
|
||||||
|
|
@ -2868,6 +2907,7 @@ impl<DestReg: Type, SrcReg: Type> ReadL2RegMOp<DestReg, SrcReg> {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let WriteL2RegMOp::<_, _> {
|
let WriteL2RegMOp::<_, _> {
|
||||||
|
|
@ -3014,6 +3054,7 @@ impl<DestReg: Type, SrcReg: Type, SrcCount: KnownSize>
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let LoadMOp::<_, _> {
|
let LoadMOp::<_, _> {
|
||||||
|
|
@ -3062,6 +3103,7 @@ impl<DestReg: Type, SrcReg: Type> LoadMOp<DestReg, SrcReg> {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let StoreMOp::<_, _> {
|
let StoreMOp::<_, _> {
|
||||||
|
|
@ -3120,6 +3162,7 @@ mop_enum! {
|
||||||
}
|
}
|
||||||
|
|
||||||
common_mop_struct! {
|
common_mop_struct! {
|
||||||
|
#[leaf]
|
||||||
#[debug(#[hdl] |this, f| {
|
#[debug(#[hdl] |this, f| {
|
||||||
#[hdl(sim)]
|
#[hdl(sim)]
|
||||||
let MoveRegMOp::<_, _> {
|
let MoveRegMOp::<_, _> {
|
||||||
|
|
|
||||||
|
|
@ -937,6 +937,7 @@ pub fn decode_and_run_single_insn<C: Type + PhantomConstCpuConfig, D: Type + Dec
|
||||||
connect(output.data, output.ty().data.HdlNone());
|
connect(output.data, output.ty().data.HdlNone());
|
||||||
#[hdl]
|
#[hdl]
|
||||||
if let HdlSome(input) = input.data {
|
if let HdlSome(input) = input.data {
|
||||||
|
connect(decoder_input, input.decoder_input);
|
||||||
connect(
|
connect(
|
||||||
state_reg,
|
state_reg,
|
||||||
HdlSome(DecodeAndRunSingleInsnState::new(
|
HdlSome(DecodeAndRunSingleInsnState::new(
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config::CpuConfig,
|
config::CpuConfig,
|
||||||
instruction::{
|
instruction::{
|
||||||
AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpRegNum, MOpTrait,
|
AluBranchMOp, LoadStoreMOp, MOp, MOpDestReg, MOpInto, MOpLeaf, MOpRegNum, MOpTrait,
|
||||||
MOpVariantVisitOps, MOpVariantVisitor, MOpVisitVariants, RenamedMOp, mop_enum,
|
MOpVariantVisitOps, MOpVariantVisitor, MOpVisitVariants, RenamedMOp, mop_enum,
|
||||||
},
|
},
|
||||||
rename_execute_retire::ExecuteToUnitInterface,
|
rename_execute_retire::ExecuteToUnitInterface,
|
||||||
|
|
@ -45,9 +45,9 @@ macro_rules! all_units {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $UnitKind {
|
impl $UnitKind {
|
||||||
pub fn unit(self, config: PhantomConst<CpuConfig>, unit_index: usize) -> DynUnit {
|
pub fn unit(self, config: PhantomConst<CpuConfig>, unit_index: usize, filter: &mut impl RenamedMOpFilter) -> DynUnit {
|
||||||
match self {
|
match self {
|
||||||
$($UnitKind::$Unit => $create_dyn_unit_fn(config, unit_index),)*
|
$($UnitKind::$Unit => $create_dyn_unit_fn(config, unit_index, filter),)*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -326,19 +326,67 @@ all_units! {
|
||||||
this.TransformedMove.mapped_ty(new_dest_reg, new_src_reg)
|
this.TransformedMove.mapped_ty(new_dest_reg, new_src_reg)
|
||||||
})] TransformedMoveOp: Type
|
})] TransformedMoveOp: Type
|
||||||
> {
|
> {
|
||||||
#[create_dyn_unit_fn = |config, unit_index| alu_branch::AluBranch::new(config, unit_index).to_dyn()]
|
#[create_dyn_unit_fn = |config, unit_index, filter| alu_branch::AluBranch::new(config, unit_index, filter).to_dyn()]
|
||||||
#[extract(alu_branch_mop, alu_branch_mop_sim, alu_branch_mop_sim_ref, alu_branch_mop_sim_mut)]
|
#[extract(alu_branch_mop, alu_branch_mop_sim, alu_branch_mop_sim_ref, alu_branch_mop_sim_mut)]
|
||||||
AluBranch(AluBranchMOp<DestReg, SrcReg>),
|
AluBranch(AluBranchMOp<DestReg, SrcReg>),
|
||||||
#[transformed_move]
|
#[transformed_move]
|
||||||
#[create_dyn_unit_fn = |config, unit_index| todo!()]
|
#[create_dyn_unit_fn = |config, unit_index, filter| todo!()]
|
||||||
#[extract(transformed_move_mop, transformed_move_mop_sim, transformed_move_mop_sim_ref, transformed_move_mop_sim_mut)]
|
#[extract(transformed_move_mop, transformed_move_mop_sim, transformed_move_mop_sim_ref, transformed_move_mop_sim_mut)]
|
||||||
TransformedMove(TransformedMoveOp),
|
TransformedMove(TransformedMoveOp),
|
||||||
#[create_dyn_unit_fn = |config, unit_index| todo!()]
|
#[create_dyn_unit_fn = |config, unit_index, filter| todo!()]
|
||||||
#[extract(load_store_mop, load_store_mop_sim, load_store_mop_sim_ref, load_store_mop_sim_mut)]
|
#[extract(load_store_mop, load_store_mop_sim, load_store_mop_sim_ref, load_store_mop_sim_mut)]
|
||||||
LoadStore(LoadStoreMOp<DestReg, SrcReg>),
|
LoadStore(LoadStoreMOp<DestReg, SrcReg>),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait RenamedMOpFilter {
|
||||||
|
fn should_include(
|
||||||
|
&mut self,
|
||||||
|
mop: &SimValue<crate::rename_execute_retire::RenamedMOp<PhantomConst<CpuConfig>>>,
|
||||||
|
) -> bool;
|
||||||
|
fn should_include_ty<
|
||||||
|
T: MOpLeaf<crate::rename_execute_retire::RenamedMOp<PhantomConst<CpuConfig>>>,
|
||||||
|
>(
|
||||||
|
&mut self,
|
||||||
|
ty: T,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.should_include(
|
||||||
|
&T::mop_into(
|
||||||
|
UInt::new_dyn(ty.canonical().bit_width())
|
||||||
|
.zero()
|
||||||
|
.to_expr()
|
||||||
|
.cast_bits_to(ty),
|
||||||
|
)
|
||||||
|
.to_sim_value(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
T: ?Sized
|
||||||
|
+ FnMut(&SimValue<crate::rename_execute_retire::RenamedMOp<PhantomConst<CpuConfig>>>) -> bool,
|
||||||
|
> RenamedMOpFilter for T
|
||||||
|
{
|
||||||
|
fn should_include(
|
||||||
|
&mut self,
|
||||||
|
mop: &SimValue<crate::rename_execute_retire::RenamedMOp<PhantomConst<CpuConfig>>>,
|
||||||
|
) -> bool {
|
||||||
|
self(mop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenamedMOpFilter for () {
|
||||||
|
fn should_include(
|
||||||
|
&mut self,
|
||||||
|
_mop: &SimValue<crate::rename_execute_retire::RenamedMOp<PhantomConst<CpuConfig>>>,
|
||||||
|
) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait UnitTrait:
|
pub trait UnitTrait:
|
||||||
'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId
|
'static + Send + Sync + std::fmt::Debug + fayalite::intern::SupportsPtrEqWithTypeId
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
ExecuteToUnitInterface, GlobalState, NextPcPredictorOp, RenamedMOp, UnitCausedCancel,
|
ExecuteToUnitInterface, GlobalState, NextPcPredictorOp, RenamedMOp, UnitCausedCancel,
|
||||||
UnitFinishCauseCancel, UnitInputsReady, UnitOutputReady,
|
UnitFinishCauseCancel, UnitInputsReady, UnitOutputReady,
|
||||||
},
|
},
|
||||||
unit::{DynUnit, DynUnitWrapper, UnitKind, UnitTrait},
|
unit::{DynUnit, DynUnitWrapper, RenamedMOpFilter, UnitKind, UnitTrait},
|
||||||
};
|
};
|
||||||
use fayalite::{
|
use fayalite::{
|
||||||
expr::CastToImpl,
|
expr::CastToImpl,
|
||||||
|
|
@ -1235,7 +1235,11 @@ fn read_special<C: Type + PhantomConstCpuConfig>(config: C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[hdl_module]
|
#[hdl_module]
|
||||||
pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
pub fn alu_branch(
|
||||||
|
config: PhantomConst<CpuConfig>,
|
||||||
|
unit_index: usize,
|
||||||
|
filter: &mut impl RenamedMOpFilter,
|
||||||
|
) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let from_execute: ExecuteToUnitInterface<PhantomConst<CpuConfig>> =
|
let from_execute: ExecuteToUnitInterface<PhantomConst<CpuConfig>> =
|
||||||
m.input(ExecuteToUnitInterface[config]);
|
m.input(ExecuteToUnitInterface[config]);
|
||||||
|
|
@ -1294,6 +1298,7 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
match mop {
|
match mop {
|
||||||
AluBranchMOp::<_, _>::AddSub(mop) => {
|
AluBranchMOp::<_, _>::AddSub(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let add_sub = instance(add_sub(config));
|
let add_sub = instance(add_sub(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1309,8 +1314,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(*mop_, mop);
|
connect(*mop_, mop);
|
||||||
connect(src_values_, src_values);
|
connect(src_values_, src_values);
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::AddSubI(mop) => {
|
AluBranchMOp::<_, _>::AddSubI(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let add_sub_i = instance(add_sub(config));
|
let add_sub_i = instance(add_sub(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1326,8 +1335,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(*mop_, mop);
|
connect(*mop_, mop);
|
||||||
connect(src_values_, src_values);
|
connect(src_values_, src_values);
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::LogicalFlags(mop) => {
|
AluBranchMOp::<_, _>::LogicalFlags(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let logical_flags = instance(logical_flags(config));
|
let logical_flags = instance(logical_flags(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1341,8 +1354,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(*mop_, mop);
|
connect(*mop_, mop);
|
||||||
connect(src_values_, src_values);
|
connect(src_values_, src_values);
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::Logical(mop) => {
|
AluBranchMOp::<_, _>::Logical(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let logical = instance(logical(config));
|
let logical = instance(logical(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1356,8 +1373,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(*mop_, mop);
|
connect(*mop_, mop);
|
||||||
connect(src_values_, src_values);
|
connect(src_values_, src_values);
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::LogicalI(mop) => {
|
AluBranchMOp::<_, _>::LogicalI(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let logical_i = instance(logical(config));
|
let logical_i = instance(logical(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1371,8 +1392,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(*mop_, mop);
|
connect(*mop_, mop);
|
||||||
connect(src_values_, src_values);
|
connect(src_values_, src_values);
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::ShiftRotate(mop) => {
|
AluBranchMOp::<_, _>::ShiftRotate(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let shift_rotate = instance(shift_rotate(config));
|
let shift_rotate = instance(shift_rotate(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1386,8 +1411,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(*mop_, mop);
|
connect(*mop_, mop);
|
||||||
connect(src_values_, src_values);
|
connect(src_values_, src_values);
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::Compare(mop) => {
|
AluBranchMOp::<_, _>::Compare(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let compare = instance(compare(config));
|
let compare = instance(compare(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1401,8 +1430,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(*mop_, mop);
|
connect(*mop_, mop);
|
||||||
connect(src_values_, src_values);
|
connect(src_values_, src_values);
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::CompareI(mop) => {
|
AluBranchMOp::<_, _>::CompareI(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let compare_i = instance(compare(config));
|
let compare_i = instance(compare(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1416,8 +1449,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(*mop_, mop);
|
connect(*mop_, mop);
|
||||||
connect(src_values_, src_values);
|
connect(src_values_, src_values);
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::Branch(mop) => {
|
AluBranchMOp::<_, _>::Branch(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let branch = instance(branch(config));
|
let branch = instance(branch(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1441,8 +1478,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
connect(predictor_op, predictor_op_);
|
connect(predictor_op, predictor_op_);
|
||||||
connect(caused_cancel, caused_cancel_);
|
connect(caused_cancel, caused_cancel_);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::BranchI(mop) => {
|
AluBranchMOp::<_, _>::BranchI(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let branch_i = instance(branch(config));
|
let branch_i = instance(branch(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1466,8 +1507,12 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
connect(predictor_op, predictor_op_);
|
connect(predictor_op, predictor_op_);
|
||||||
connect(caused_cancel, caused_cancel_);
|
connect(caused_cancel, caused_cancel_);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AluBranchMOp::<_, _>::ReadSpecial(mop) => {
|
AluBranchMOp::<_, _>::ReadSpecial(mop) => {
|
||||||
|
if filter.should_include_ty(mop.ty()) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let read_special = instance(read_special(config));
|
let read_special = instance(read_special(config));
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -1481,6 +1526,9 @@ pub fn alu_branch(config: PhantomConst<CpuConfig>, unit_index: usize) {
|
||||||
connect(*mop_, mop);
|
connect(*mop_, mop);
|
||||||
connect(src_values_, src_values);
|
connect(src_values_, src_values);
|
||||||
connect(dest_value, output);
|
connect(dest_value, output);
|
||||||
|
} else {
|
||||||
|
connect(*dest_value, PRegValue::zeroed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connect(
|
connect(
|
||||||
|
|
@ -1530,10 +1578,14 @@ pub struct AluBranch {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AluBranch {
|
impl AluBranch {
|
||||||
pub fn new(config: PhantomConst<CpuConfig>, unit_index: usize) -> Self {
|
pub fn new(
|
||||||
|
config: PhantomConst<CpuConfig>,
|
||||||
|
unit_index: usize,
|
||||||
|
filter: &mut impl RenamedMOpFilter,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
config,
|
config,
|
||||||
module: alu_branch(config, unit_index),
|
module: alu_branch(config, unit_index, filter),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28278
crates/cpu/tests/expected/units_formal_power_isa_add_sim.vcd
generated
Normal file
28278
crates/cpu/tests/expected/units_formal_power_isa_add_sim.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -4079,7 +4079,7 @@ fn rename_execute_retire_test_harness<#[hdl(skip)] MI: MakeInsns>(
|
||||||
AluBranchKind::Real => {
|
AluBranchKind::Real => {
|
||||||
let unit = instance_with_loc(
|
let unit = instance_with_loc(
|
||||||
&dut.ty().to_units.unit_field_name(unit_index),
|
&dut.ty().to_units.unit_field_name(unit_index),
|
||||||
cpu::unit::alu_branch::alu_branch(config, unit_index),
|
cpu::unit::alu_branch::alu_branch(config, unit_index, &mut ()),
|
||||||
SourceLocation::caller(),
|
SourceLocation::caller(),
|
||||||
);
|
);
|
||||||
connect(unit.from_execute, to_unit);
|
connect(unit.from_execute, to_unit);
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ fn test_test_cases_assembly() -> std::io::Result<()> {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decode_insn() {
|
fn test_decode_insn() {
|
||||||
let _n = SourceLocation::normalize_files_for_tests();
|
let _n = SourceLocation::normalize_files_for_tests();
|
||||||
let m = decode_one_insn();
|
let m = decode_one_insn(());
|
||||||
let mut sim = Simulation::new(m);
|
let mut sim = Simulation::new(m);
|
||||||
let _checked_vcd_output = checked_vcd_output!(
|
let _checked_vcd_output = checked_vcd_output!(
|
||||||
&mut sim,
|
&mut sim,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
use cpu::{
|
use cpu::{
|
||||||
config::{CpuConfig, UnitConfig},
|
config::{CpuConfig, UnitConfig},
|
||||||
decoder::simple_power_isa,
|
decoder::simple_power_isa::{self, DecodeFilter, DecodeFilterArgs},
|
||||||
instruction::MOpRegNum,
|
instruction::{AluBranchMOp, MOpRegNum},
|
||||||
next_pc::CallStackOp,
|
next_pc::CallStackOp,
|
||||||
register::{FlagsMode, PRegFlags, PRegFlagsPowerISA, PRegFlagsPowerISAView, PRegValue},
|
register::{FlagsMode, PRegFlags, PRegFlagsPowerISA, PRegFlagsPowerISAView, PRegValue},
|
||||||
rename_execute_retire::{
|
rename_execute_retire::{
|
||||||
|
|
@ -14,7 +14,7 @@ use cpu::{
|
||||||
DecodeAndRunSingleInsnInput, DecodeAndRunSingleInsnOutput, DecodeOneInsnInput,
|
DecodeAndRunSingleInsnInput, DecodeAndRunSingleInsnOutput, DecodeOneInsnInput,
|
||||||
DecodeOneInsnMaxMOpCount, decode_and_run_single_insn,
|
DecodeOneInsnMaxMOpCount, decode_and_run_single_insn,
|
||||||
},
|
},
|
||||||
unit::{UnitKind, UnitTrait},
|
unit::{RenamedMOpFilter, UnitKind, UnitMOp, UnitTrait},
|
||||||
util::array_vec::ArrayVec,
|
util::array_vec::ArrayVec,
|
||||||
};
|
};
|
||||||
use fayalite::{
|
use fayalite::{
|
||||||
|
|
@ -26,7 +26,11 @@ use fayalite::{
|
||||||
use std::num::NonZero;
|
use std::num::NonZero;
|
||||||
|
|
||||||
#[hdl_module]
|
#[hdl_module]
|
||||||
fn formal_harness(config: PhantomConst<CpuConfig>) {
|
fn formal_harness(
|
||||||
|
config: PhantomConst<CpuConfig>,
|
||||||
|
decode_filter: impl DecodeFilter,
|
||||||
|
renamed_mop_filter: &mut impl RenamedMOpFilter,
|
||||||
|
) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let cd: ClockDomain = m.input();
|
let cd: ClockDomain = m.input();
|
||||||
|
|
||||||
|
|
@ -47,7 +51,7 @@ fn formal_harness(config: PhantomConst<CpuConfig>) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let decode_and_run = instance(decode_and_run_single_insn(
|
let decode_and_run = instance(decode_and_run_single_insn(
|
||||||
config,
|
config,
|
||||||
simple_power_isa::decode_one_insn(),
|
simple_power_isa::decode_one_insn(decode_filter),
|
||||||
));
|
));
|
||||||
connect(decode_and_run.cd, cd);
|
connect(decode_and_run.cd, cd);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
|
@ -78,7 +82,7 @@ fn formal_harness(config: PhantomConst<CpuConfig>) {
|
||||||
connect(decode_and_run.output.ready, true);
|
connect(decode_and_run.output.ready, true);
|
||||||
connect(output, decode_and_run.output.data);
|
connect(output, decode_and_run.output.data);
|
||||||
for (unit_index, unit) in config.get().units.iter().enumerate() {
|
for (unit_index, unit) in config.get().units.iter().enumerate() {
|
||||||
let dyn_unit = unit.kind.unit(config, unit_index);
|
let dyn_unit = unit.kind.unit(config, unit_index, renamed_mop_filter);
|
||||||
let unit = instance_with_loc(
|
let unit = instance_with_loc(
|
||||||
&decode_and_run.ty().to_units.unit_field_name(unit_index),
|
&decode_and_run.ty().to_units.unit_field_name(unit_index),
|
||||||
dyn_unit.module(),
|
dyn_unit.module(),
|
||||||
|
|
@ -95,24 +99,70 @@ fn formal_harness(config: PhantomConst<CpuConfig>) {
|
||||||
hdl_assert(cd.clk, !decode_and_run.error, "");
|
hdl_assert(cd.clk, !decode_and_run.error, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct R3R4AnyConst {
|
||||||
|
r3_any_const: Expr<UInt<64>>,
|
||||||
|
r4_any_const: Expr<UInt<64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl R3R4AnyConst {
|
||||||
|
#[track_caller]
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
r3_any_const: any_const(StaticType::TYPE),
|
||||||
|
r4_any_const: any_const(StaticType::TYPE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[hdl_module]
|
#[hdl_module]
|
||||||
fn check_power_isa_add_formal(config: PhantomConst<CpuConfig>) {
|
fn check_power_isa_add_formal(
|
||||||
#[hdl]
|
config: PhantomConst<CpuConfig>,
|
||||||
let clk: Clock = m.input();
|
r3_r4_any_const: Option<R3R4AnyConst>,
|
||||||
|
) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let cd = wire();
|
let cd = wire();
|
||||||
connect(
|
connect(
|
||||||
cd,
|
cd,
|
||||||
#[hdl]
|
#[hdl]
|
||||||
ClockDomain {
|
ClockDomain {
|
||||||
clk,
|
clk: formal_global_clock(),
|
||||||
rst: formal_reset().to_reset(),
|
rst: formal_reset().to_reset(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let harness = instance(formal_harness(config));
|
let harness = instance(formal_harness(
|
||||||
|
config,
|
||||||
|
|args: &DecodeFilterArgs<'_>| args.mnemonic() == "add",
|
||||||
|
&mut |mop: &SimValue<_>| -> bool {
|
||||||
|
#[hdl(sim)]
|
||||||
|
match mop {
|
||||||
|
UnitMOp::<_, _, _>::AluBranch(mop) =>
|
||||||
|
{
|
||||||
|
#[hdl(sim)]
|
||||||
|
match mop {
|
||||||
|
AluBranchMOp::<_, _>::AddSub(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
connect(harness.cd, cd);
|
connect(harness.cd, cd);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
let ran: Bool = m.output();
|
||||||
|
#[hdl]
|
||||||
|
let ran_reg = reg_builder().clock_domain(cd).reset(false);
|
||||||
|
connect(ran, ran_reg);
|
||||||
|
#[hdl]
|
||||||
|
let cycle_count = reg_builder().clock_domain(cd).reset(0u32);
|
||||||
|
connect_any(cycle_count, cycle_count + 1u32);
|
||||||
|
#[hdl]
|
||||||
|
if cycle_count.cmp_ge(5u32) {
|
||||||
|
hdl_assert(cd.clk, ran, "");
|
||||||
|
}
|
||||||
|
#[hdl]
|
||||||
let DecodeAndRunSingleInsnInput::<_, _> {
|
let DecodeAndRunSingleInsnInput::<_, _> {
|
||||||
decoder_input,
|
decoder_input,
|
||||||
fetch_block_id,
|
fetch_block_id,
|
||||||
|
|
@ -135,12 +185,16 @@ fn check_power_isa_add_formal(config: PhantomConst<CpuConfig>) {
|
||||||
1 << MOpRegNum::WIDTH,
|
1 << MOpRegNum::WIDTH,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
let R3R4AnyConst {
|
||||||
|
r3_any_const,
|
||||||
|
r4_any_const,
|
||||||
|
} = r3_r4_any_const.unwrap_or_else(|| R3R4AnyConst::new());
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let input_r3 = wire();
|
let input_r3 = wire();
|
||||||
connect(input_r3, any_const(StaticType::TYPE));
|
connect(input_r3, r3_any_const);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let input_r4 = wire();
|
let input_r4 = wire();
|
||||||
connect(input_r4, any_const(StaticType::TYPE));
|
connect(input_r4, r4_any_const);
|
||||||
connect(
|
connect(
|
||||||
input_regs.regs[MOpRegNum::power_isa_gpr_reg_imm(3).value].int_fp,
|
input_regs.regs[MOpRegNum::power_isa_gpr_reg_imm(3).value].int_fp,
|
||||||
input_r3,
|
input_r3,
|
||||||
|
|
@ -151,6 +205,7 @@ fn check_power_isa_add_formal(config: PhantomConst<CpuConfig>) {
|
||||||
);
|
);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
if let HdlSome(output) = harness.output {
|
if let HdlSome(output) = harness.output {
|
||||||
|
connect(ran_reg, true);
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let DecodeAndRunSingleInsnOutput::<_, _> {
|
let DecodeAndRunSingleInsnOutput::<_, _> {
|
||||||
regs,
|
regs,
|
||||||
|
|
@ -218,13 +273,12 @@ fn test_power_isa_add_formal() {
|
||||||
vec![UnitConfig::new(UnitKind::AluBranch)],
|
vec![UnitConfig::new(UnitKind::AluBranch)],
|
||||||
NonZero::new(20).unwrap(),
|
NonZero::new(20).unwrap(),
|
||||||
));
|
));
|
||||||
let m = check_power_isa_add_formal(config);
|
let m = check_power_isa_add_formal(config, None);
|
||||||
println!("starting assert formal");
|
|
||||||
assert_formal(
|
assert_formal(
|
||||||
"test_power_isa_add_formal",
|
"test_power_isa_add_formal",
|
||||||
m,
|
m,
|
||||||
FormalMode::Prove,
|
FormalMode::BMC,
|
||||||
10,
|
7,
|
||||||
None,
|
None,
|
||||||
ExportOptions {
|
ExportOptions {
|
||||||
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
|
simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts),
|
||||||
|
|
@ -232,3 +286,34 @@ fn test_power_isa_add_formal() {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[hdl]
|
||||||
|
#[test]
|
||||||
|
fn test_power_isa_add_sim() {
|
||||||
|
let config = PhantomConst::new_sized(CpuConfig::new(
|
||||||
|
vec![UnitConfig::new(UnitKind::AluBranch)],
|
||||||
|
NonZero::new(20).unwrap(),
|
||||||
|
));
|
||||||
|
let r3_r4_any_const = R3R4AnyConst::new();
|
||||||
|
let m = check_power_isa_add_formal(config, Some(r3_r4_any_const));
|
||||||
|
let mut sim = Simulation::new(m);
|
||||||
|
let _checked_vcd_output = cpu::checked_vcd_output!(
|
||||||
|
&mut sim,
|
||||||
|
"tests/expected/units_formal_power_isa_add_sim.vcd",
|
||||||
|
);
|
||||||
|
sim.write(r3_r4_any_const.r3_any_const, 0x1234u64);
|
||||||
|
sim.write(r3_r4_any_const.r4_any_const, 0x2345u64);
|
||||||
|
let clk = formal_global_clock();
|
||||||
|
let rst = formal_reset();
|
||||||
|
sim.write_clock(clk, false);
|
||||||
|
sim.write_reset(rst, true);
|
||||||
|
for cycle in 0..10 {
|
||||||
|
sim.advance_time(SimDuration::from_nanos(500));
|
||||||
|
println!("clock tick: {cycle}");
|
||||||
|
sim.write_clock(clk, true);
|
||||||
|
sim.advance_time(SimDuration::from_nanos(500));
|
||||||
|
sim.write_clock(clk, false);
|
||||||
|
sim.write_reset(rst, false);
|
||||||
|
}
|
||||||
|
assert!(sim.read_bool(sim.io().ran));
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue