319 lines
10 KiB
Rust
319 lines
10 KiB
Rust
// 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<CpuConfig>,
|
|
decode_filter: impl DecodeFilter,
|
|
renamed_mop_filter: &mut impl RenamedMOpFilter,
|
|
) {
|
|
#[hdl]
|
|
let cd: ClockDomain = m.input();
|
|
|
|
#[hdl]
|
|
let input: DecodeAndRunSingleInsnInput<
|
|
PhantomConst<CpuConfig>,
|
|
DecodeOneInsnInput<simple_power_isa::decode_one_insn>,
|
|
> = m.input(DecodeAndRunSingleInsnInput[config][StaticType::TYPE]);
|
|
|
|
#[hdl]
|
|
let output: HdlOption<
|
|
DecodeAndRunSingleInsnOutput<
|
|
PhantomConst<CpuConfig>,
|
|
DecodeOneInsnMaxMOpCount<simple_power_isa::decode_one_insn>,
|
|
>,
|
|
> = 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<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]
|
|
fn check_power_isa_add_formal(
|
|
config: PhantomConst<CpuConfig>,
|
|
r3_r4_any_const: Option<R3R4AnyConst>,
|
|
) {
|
|
#[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::<PRegFlagsPowerISA>(expected.flags);
|
|
let input_r3_s = input_r3.cast_to_static::<SInt<64>>();
|
|
let input_r4_s = input_r4.cast_to_static::<SInt<64>>();
|
|
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));
|
|
}
|