// SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use cpu::{ config::{CpuConfig, UnitConfig}, decoder::simple_power_isa::{self, DecodeFilter, DecodeFilterArgs}, instruction::{AluBranchMOp, MOpRegNum}, next_pc::CallStackOp, register::{FlagsMode, PRegFlags, PRegFlagsPowerISA, PRegFlagsPowerISAView, PRegValue}, rename_execute_retire::{ GlobalState, NextPcPredictorOp, to_unit_interfaces::ExecuteToUnitInterfaces, }, test::decode_and_run_single_insn::{ DecodeAndRunSingleInsnInput, DecodeAndRunSingleInsnOutput, DecodeOneInsnInput, DecodeOneInsnMaxMOpCount, decode_and_run_single_insn, }, unit::{RenamedMOpFilter, UnitKind, UnitMOp, UnitTrait}, util::array_vec::ArrayVec, }; use fayalite::{ firrtl::ExportOptions, module::{instance_with_loc, transform::simplify_enums::SimplifyEnumsKind}, prelude::*, ty::StaticType, }; use std::num::NonZero; #[hdl_module] fn formal_harness( config: PhantomConst, decode_filter: impl DecodeFilter, renamed_mop_filter: &mut impl RenamedMOpFilter, ) { #[hdl] let cd: ClockDomain = m.input(); #[hdl] let input: DecodeAndRunSingleInsnInput< PhantomConst, DecodeOneInsnInput, > = m.input(DecodeAndRunSingleInsnInput[config][StaticType::TYPE]); #[hdl] let output: HdlOption< DecodeAndRunSingleInsnOutput< PhantomConst, DecodeOneInsnMaxMOpCount, >, > = m.output(HdlOption[DecodeAndRunSingleInsnOutput[config][ConstUsize]]); #[hdl] let decode_and_run = instance(decode_and_run_single_insn( config, simple_power_isa::decode_one_insn(decode_filter), )); connect(decode_and_run.cd, cd); #[hdl] let need_to_send_input_reg = reg_builder().clock_domain(cd).reset(true); #[hdl] if need_to_send_input_reg { connect(decode_and_run.input.data, HdlSome(input)); #[hdl] if decode_and_run.input.ready { connect(need_to_send_input_reg, false); } } else { connect( decode_and_run.input.data, decode_and_run.ty().input.data.HdlNone(), ); } connect( decode_and_run.global_state, #[hdl] GlobalState { flags_mode: FlagsMode.PowerISA( #[hdl] PRegFlagsPowerISA {}, ), }, ); connect(decode_and_run.output.ready, true); connect(output, decode_and_run.output.data); for (unit_index, unit) in config.get().units.iter().enumerate() { let dyn_unit = unit.kind.unit(config, unit_index, renamed_mop_filter); let unit = instance_with_loc( &decode_and_run.ty().to_units.unit_field_name(unit_index), dyn_unit.module(), SourceLocation::caller(), ); connect( dyn_unit.from_execute(unit), ExecuteToUnitInterfaces::unit_fields(decode_and_run.to_units)[unit_index], ); if let Some(unit_cd) = dyn_unit.cd(unit) { connect(unit_cd, cd); } } hdl_assert(cd.clk, !decode_and_run.error, ""); } #[derive(Copy, Clone)] struct R3R4AnyConst { r3_any_const: Expr>, r4_any_const: Expr>, } impl R3R4AnyConst { #[track_caller] fn new() -> Self { Self { r3_any_const: any_const(StaticType::TYPE), r4_any_const: any_const(StaticType::TYPE), } } } #[hdl_module] fn check_power_isa_add_formal( config: PhantomConst, r3_r4_any_const: Option, ) { #[hdl] let cd = wire(); connect( cd, #[hdl] ClockDomain { clk: formal_global_clock(), rst: formal_reset().to_reset(), }, ); #[hdl] 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); #[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::<_, _> { decoder_input, fetch_block_id, first_id, pc, predicted_next_pc, regs: input_regs, config: _, } = harness.input; // add r3, r3, r4 connect(decoder_input, (0x7c632214_u32, HdlNone())); connect(fetch_block_id, 0u8); connect(first_id, 0u16); connect(pc, 0x1000_u64); connect(predicted_next_pc, 0x1004_u64); connect( input_regs.regs, repeat( PRegValue::zeroed().to_trace_as_string(), 1 << MOpRegNum::WIDTH, ), ); let R3R4AnyConst { r3_any_const, r4_any_const, } = r3_r4_any_const.unwrap_or_else(|| R3R4AnyConst::new()); #[hdl] let input_r3 = wire(); connect(input_r3, r3_any_const); #[hdl] let input_r4 = wire(); connect(input_r4, r4_any_const); connect( input_regs.regs[MOpRegNum::power_isa_gpr_reg_imm(3).value].int_fp, input_r3, ); connect( input_regs.regs[MOpRegNum::power_isa_gpr_reg_imm(4).value].int_fp, input_r4, ); #[hdl] if let HdlSome(output) = harness.output { connect(ran_reg, true); #[hdl] let DecodeAndRunSingleInsnOutput::<_, _> { regs, cancel_and_start_at, retired_insns, config: _, } = output; hdl_assert(cd.clk, cancel_and_start_at.cmp_eq(HdlNone()), ""); hdl_assert(cd.clk, ArrayVec::len(retired_insns).cmp_eq(1u8), ""); #[hdl] let NextPcPredictorOp::<_> { call_stack_op, cond_br_taken, config: _, } = retired_insns[0]; hdl_assert(cd.clk, call_stack_op.cmp_eq(CallStackOp.None()), ""); hdl_assert(cd.clk, cond_br_taken.cmp_eq(HdlNone()), ""); #[hdl] let expected_regs = wire(regs.ty()); for (reg_index, expected) in expected_regs.regs.into_iter().enumerate() { let reg_num = reg_index as u32; if reg_num == MOpRegNum::power_isa_gpr_reg_num(3) { connect(expected.int_fp, (input_r3 + input_r4).cast_to_static()); let PRegFlagsPowerISAView { unused: _, xer_ca, xer_ca32, xer_ov, xer_ov32, cr_lt, cr_gt, cr_eq, so, .. } = PRegFlags::view::(expected.flags); let input_r3_s = input_r3.cast_to_static::>(); let input_r4_s = input_r4.cast_to_static::>(); let u64_sum = input_r3 + input_r4; let s64_sum = input_r3_s + input_r4_s; let u32_sum = input_r3.cast_to(UInt[32]) + input_r4.cast_to(UInt[32]); let s32_sum = input_r3.cast_to(SInt[32]) + input_r4.cast_to(SInt[32]); let sum_as_s64 = u64_sum.cast_to(SInt[64]); connect(xer_ca, u64_sum[64]); connect(xer_ca32, u32_sum[32]); connect(xer_ov, s64_sum.cmp_lt(i64::MIN) | s64_sum.cmp_gt(i64::MAX)); connect( xer_ov32, s32_sum.cmp_lt(i32::MIN) | s32_sum.cmp_gt(i32::MAX), ); connect(cr_gt, sum_as_s64.cmp_gt(0i64)); connect(cr_lt, sum_as_s64.cmp_lt(0i64)); connect(cr_eq, sum_as_s64.cmp_eq(0i64)); connect(so, xer_ov); // TODO: also propagate from input SO } else { connect(expected, input_regs.regs[reg_index]); } } hdl_assert(cd.clk, expected_regs.cmp_eq(regs), ""); } } #[test] fn test_power_isa_add_formal() { let config = PhantomConst::new_sized(CpuConfig::new( vec![UnitConfig::new(UnitKind::AluBranch)], NonZero::new(20).unwrap(), )); let m = check_power_isa_add_formal(config, None); assert_formal( "test_power_isa_add_formal", m, FormalMode::BMC, 7, None, ExportOptions { simplify_enums: Some(SimplifyEnumsKind::ReplaceWithBundleOfUInts), ..Default::default() }, ); } #[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)); }