sim: add support for registers
All checks were successful
/ deps (push) Successful in 19s
/ test (push) Successful in 5m1s
/ deps (pull_request) Successful in 14s
/ test (pull_request) Successful in 5m0s

This commit is contained in:
Jacob Lifshay 2024-12-01 20:14:13 -08:00
parent 5e0548db26
commit fd45465d35
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
3 changed files with 2944 additions and 57 deletions

View file

@ -21,6 +21,7 @@ use crate::{
StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg, StmtWire, TargetInInstantiatedModule,
},
prelude::*,
reset::{ResetType, ResetTypeDispatch},
sim::{
interpreter::{
Insn, InsnField, InsnFieldKind, InsnFieldType, Insns, InsnsBuilding, InsnsBuildingDone,
@ -208,6 +209,14 @@ impl<T: Type> CompiledValue<T> {
fn write(self) -> (CompiledTypeLayout<T>, TypeIndexRange) {
self.write.unwrap_or((self.layout, self.range))
}
fn write_value(self) -> Self {
let (layout, range) = self.write();
Self {
layout,
range,
write: None,
}
}
fn map<U: Type>(
self,
mut f: impl FnMut(
@ -1180,6 +1189,29 @@ impl Assignment {
}
}
#[derive(Debug)]
struct RegisterReset {
is_async: bool,
init: CompiledValue<CanonicalType>,
rst: StatePartIndex<StatePartKindSmallSlots>,
}
#[derive(Debug, Clone, Copy)]
struct ClockTrigger {
last_clk_was_low: StatePartIndex<StatePartKindSmallSlots>,
clk: StatePartIndex<StatePartKindSmallSlots>,
clk_triggered: StatePartIndex<StatePartKindSmallSlots>,
source_location: SourceLocation,
}
#[derive(Debug)]
struct Register {
value: CompiledValue<CanonicalType>,
clk_triggered: StatePartIndex<StatePartKindSmallSlots>,
reset: Option<RegisterReset>,
source_location: SourceLocation,
}
#[derive(Debug)]
pub struct Compiler {
insns: Insns<InsnsBuilding>,
@ -1192,7 +1224,12 @@ pub struct Compiler {
decl_conditions: HashMap<TargetInInstantiatedModule, Interned<[Cond]>>,
compiled_values_to_dyn_array_indexes:
HashMap<CompiledValue<UInt>, StatePartIndex<StatePartKindSmallSlots>>,
compiled_value_bool_dest_is_small_map:
HashMap<CompiledValue<CanonicalType>, StatePartIndex<StatePartKindSmallSlots>>,
assignments: Assignments,
clock_triggers: Vec<ClockTrigger>,
compiled_value_to_clock_trigger_map: HashMap<CompiledValue<Clock>, ClockTrigger>,
registers: Vec<Register>,
traces: Vec<SimTrace<()>>,
}
@ -1211,7 +1248,11 @@ impl Compiler {
compiled_exprs_to_values: HashMap::new(),
decl_conditions: HashMap::new(),
compiled_values_to_dyn_array_indexes: HashMap::new(),
compiled_value_bool_dest_is_small_map: HashMap::new(),
assignments: Assignments::default(),
clock_triggers: Vec::new(),
compiled_value_to_clock_trigger_map: HashMap::new(),
registers: Vec::new(),
traces: Vec::new(),
}
}
@ -1664,6 +1705,48 @@ impl Compiler {
.insert(compiled_value, retval);
retval
}
fn compiled_value_bool_dest_is_small(
&mut self,
compiled_value: CompiledValue<CanonicalType>,
source_location: SourceLocation,
) -> StatePartIndex<StatePartKindSmallSlots> {
if let Some(&retval) = self
.compiled_value_bool_dest_is_small_map
.get(&compiled_value)
{
return retval;
}
let retval = match compiled_value.range.len() {
TypeLen::A_SMALL_SLOT => compiled_value.range.small_slots.start,
TypeLen::A_BIG_SLOT => {
let debug_data = SlotDebugData {
name: Interned::default(),
ty: Bool.canonical(),
};
let dest = self
.insns
.allocate_variable(&TypeLayout {
small_slots: StatePartLayout::scalar(debug_data),
big_slots: StatePartLayout::empty(),
})
.small_slots
.start;
self.add_assignment(
Interned::default(),
vec![Insn::IsNonZeroDestIsSmall {
dest,
src: compiled_value.range.big_slots.start,
}],
source_location,
);
dest
}
_ => unreachable!(),
};
self.compiled_value_bool_dest_is_small_map
.insert(compiled_value, retval);
retval
}
fn compile_expr(
&mut self,
instantiated_module: InstantiatedModule,
@ -2626,6 +2709,125 @@ impl Compiler {
source_location,
);
}
fn compile_clock(
&mut self,
clk: CompiledValue<Clock>,
source_location: SourceLocation,
) -> ClockTrigger {
if let Some(&retval) = self.compiled_value_to_clock_trigger_map.get(&clk) {
return retval;
}
let mut alloc_small_slot = |part_name: &str| {
self.insns
.state_layout
.ty
.small_slots
.allocate(&StatePartLayout::scalar(SlotDebugData {
name: Interned::default(),
ty: Bool.canonical(),
}))
.start
};
let last_clk_was_low = alloc_small_slot("last_clk_was_low");
let clk_triggered = alloc_small_slot("clk_triggered");
let retval = ClockTrigger {
last_clk_was_low,
clk: self.compiled_value_bool_dest_is_small(
clk.map_ty(CanonicalType::Clock),
source_location,
),
clk_triggered,
source_location,
};
self.add_assignment(
Interned::default(),
[Insn::AndSmall {
dest: clk_triggered,
lhs: retval.clk,
rhs: last_clk_was_low,
}],
source_location,
);
self.clock_triggers.push(retval);
self.compiled_value_to_clock_trigger_map.insert(clk, retval);
retval
}
fn compile_stmt_reg<R: ResetType>(
&mut self,
stmt_reg: StmtReg<R>,
instantiated_module: InstantiatedModule,
value: CompiledValue<CanonicalType>,
) {
let StmtReg { annotations, reg } = stmt_reg;
let clk = self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().clk));
let clk = self
.compiled_expr_to_value(clk, reg.source_location())
.map_ty(Clock::from_canonical);
let clk = self.compile_clock(clk, reg.source_location());
struct Dispatch;
impl ResetTypeDispatch for Dispatch {
type Input<T: ResetType> = ();
type Output<T: ResetType> = bool;
fn reset(self, _input: Self::Input<Reset>) -> Self::Output<Reset> {
unreachable!()
}
fn sync_reset(self, _input: Self::Input<SyncReset>) -> Self::Output<SyncReset> {
false
}
fn async_reset(self, _input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset> {
true
}
}
let reset = if let Some(init) = reg.init() {
let init = self.compile_expr(instantiated_module, init);
let init = self.compiled_expr_to_value(init, reg.source_location());
let rst =
self.compile_expr(instantiated_module, Expr::canonical(reg.clock_domain().rst));
let rst = self.compiled_expr_to_value(rst, reg.source_location());
let rst = self.compiled_value_bool_dest_is_small(rst, reg.source_location());
let is_async = R::dispatch((), Dispatch);
if is_async {
let cond = Expr::canonical(reg.clock_domain().rst.cast_to(Bool));
let cond = self.compile_expr(instantiated_module, cond);
let cond = self.compiled_expr_to_value(cond, reg.source_location());
let cond = cond.map_ty(Bool::from_canonical);
// write to the register's current value since asynchronous reset is combinational
let lhs = CompiledValue {
layout: value.layout,
range: value.range,
write: None,
}
.into();
self.compile_simple_connect(
[Cond {
body: CondBody::IfTrue { cond },
source_location: reg.source_location(),
}][..]
.intern(),
lhs,
init,
reg.source_location(),
);
}
Some(RegisterReset {
is_async,
init,
rst,
})
} else {
None
};
self.registers.push(Register {
value,
clk_triggered: clk.clk_triggered,
reset,
source_location: reg.source_location(),
});
}
fn compile_declaration(
&mut self,
declaration: StmtDeclaration,
@ -2644,17 +2846,17 @@ impl Compiler {
target: target_base.into(),
};
self.decl_conditions.insert(target, conditions);
self.compile_value(target);
let compiled_value = self.compile_value(target);
match declaration {
StmtDeclaration::Wire(StmtWire { annotations, wire }) => {}
StmtDeclaration::Reg(StmtReg { annotations, reg }) => {
todo!();
StmtDeclaration::Reg(_) => {
unreachable!("Reset types were already replaced by SyncReset or AsyncReset");
}
StmtDeclaration::RegSync(StmtReg { annotations, reg }) => {
todo!();
StmtDeclaration::RegSync(stmt_reg) => {
self.compile_stmt_reg(stmt_reg, *parent_module, compiled_value)
}
StmtDeclaration::RegAsync(StmtReg { annotations, reg }) => {
todo!();
StmtDeclaration::RegAsync(stmt_reg) => {
self.compile_stmt_reg(stmt_reg, *parent_module, compiled_value)
}
StmtDeclaration::Instance(StmtInstance {
annotations,
@ -2938,10 +3140,109 @@ impl Compiler {
}
}
}
fn process_clocks(&mut self) -> Interned<[StatePartIndex<StatePartKindSmallSlots>]> {
mem::take(&mut self.clock_triggers)
.into_iter()
.map(
|ClockTrigger {
last_clk_was_low,
clk,
clk_triggered,
source_location,
}| {
self.insns.push(
Insn::NotSmall {
dest: last_clk_was_low,
src: clk,
},
source_location,
);
clk_triggered
},
)
.collect()
}
fn process_registers(&mut self) {
for Register {
value,
clk_triggered,
reset,
source_location,
} in mem::take(&mut self.registers)
{
let write_to_current_value = |value_to_write: CompiledValue<CanonicalType>| {
let TypeIndexRange {
small_slots,
big_slots,
} = value.range;
small_slots
.iter()
.zip(value_to_write.range.small_slots.iter())
.map(|(base, src)| Insn::CopySmall { dest: base, src })
.chain(
big_slots
.iter()
.zip(value_to_write.range.big_slots.iter())
.map(|(base, src)| Insn::Copy { dest: base, src }),
)
};
match reset {
Some(RegisterReset {
is_async,
init,
rst,
}) => {
let reg_end = self.insns.new_label();
let reg_reset = self.insns.new_label();
let branch_if_reset = Insn::BranchIfSmallNonZero {
target: reg_reset,
value: rst,
};
let branch_if_not_triggered = Insn::BranchIfSmallZero {
target: reg_end,
value: clk_triggered,
};
if is_async {
self.insns.push(branch_if_reset, source_location);
self.insns.push(branch_if_not_triggered, source_location);
} else {
self.insns.push(branch_if_not_triggered, source_location);
self.insns.push(branch_if_reset, source_location);
}
for insn in write_to_current_value(value.write_value()) {
self.insns.push(insn, source_location);
}
self.insns
.push(Insn::Branch { target: reg_end }, source_location);
self.insns.define_label_at_next_insn(reg_reset);
for insn in write_to_current_value(init) {
self.insns.push(insn, source_location);
}
self.insns.define_label_at_next_insn(reg_end);
}
None => {
let reg_end = self.insns.new_label();
self.insns.push(
Insn::BranchIfSmallZero {
target: reg_end,
value: clk_triggered,
},
source_location,
);
for insn in write_to_current_value(value.write_value()) {
self.insns.push(insn, source_location);
}
self.insns.define_label_at_next_insn(reg_end);
}
}
}
}
pub fn compile(mut self) -> Compiled<Bundle> {
let base_module =
*self.compile_module(InstantiatedModule::Base(self.base_module).intern_sized());
self.process_assignments();
self.process_registers();
let clocks_triggered = self.process_clocks();
self.insns
.push(Insn::Return, self.base_module.source_location());
Compiled {
@ -2956,6 +3257,7 @@ impl Compiler {
self.original_base_module.source_location(),
),
traces: Intern::intern_owned(self.traces),
clocks_triggered,
}
}
}
@ -2972,6 +3274,7 @@ pub struct Compiled<T: BundleType> {
base_module: CompiledModule,
io: Instance<T>,
traces: Interned<[SimTrace<()>]>,
clocks_triggered: Interned<[StatePartIndex<StatePartKindSmallSlots>]>,
}
impl<T: BundleType> Compiled<T> {
@ -2984,12 +3287,14 @@ impl<T: BundleType> Compiled<T> {
base_module,
io,
traces,
clocks_triggered,
} = self;
Compiled {
insns,
base_module,
io: Instance::from_canonical(io.canonical()),
traces,
clocks_triggered,
}
}
pub fn from_canonical(canonical: Compiled<Bundle>) -> Self {
@ -2998,12 +3303,14 @@ impl<T: BundleType> Compiled<T> {
base_module,
io,
traces,
clocks_triggered,
} = canonical;
Self {
insns,
base_module,
io: Instance::from_canonical(io.canonical()),
traces,
clocks_triggered,
}
}
}
@ -3621,6 +3928,7 @@ struct SimulationImpl {
traces: Box<[SimTrace<BitVec>]>,
trace_writers: Vec<TraceWriterState<DynTraceWriterDecls>>,
instant: SimInstant,
clocks_triggered: Interned<[StatePartIndex<StatePartKindSmallSlots>]>,
}
impl SimulationImpl {
@ -3683,6 +3991,7 @@ impl SimulationImpl {
)),
trace_writers: vec![],
instant: SimInstant::START,
clocks_triggered: compiled.clocks_triggered,
};
let io_target = Target::from(compiled.io);
for (BundleField { name, .. }, value) in compiled
@ -3802,7 +4111,7 @@ impl SimulationImpl {
}
#[track_caller]
fn advance_time(&mut self, duration: SimDuration) {
self.settle_step();
self.settle();
self.instant += duration;
self.for_each_trace_writer_storing_error(|this, mut trace_writer_state| {
match &mut trace_writer_state {
@ -3816,37 +4125,43 @@ impl SimulationImpl {
});
}
#[track_caller]
fn settle_step(&mut self) {
fn settle(&mut self) {
assert!(
self.uninitialized_inputs.is_empty(),
"didn't initialize all inputs",
);
if !self.needs_settle {
return;
for _ in 0..100000 {
if !self.needs_settle {
return;
}
self.state.setup_call(0);
self.state.run();
if self.made_initial_step {
self.read_traces::<false>();
} else {
self.read_traces::<true>();
}
self.made_initial_step = true;
self.needs_settle = self
.clocks_triggered
.iter()
.any(|i| self.state.small_slots[*i] != 0);
self.for_each_trace_writer_storing_error(|this, trace_writer_state| {
Ok(match trace_writer_state {
TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running(
this.init_trace_writer(trace_writer_decls.write_decls(this.trace_decls)?)?,
),
TraceWriterState::Init(trace_writer) => {
TraceWriterState::Running(this.init_trace_writer(trace_writer)?)
}
TraceWriterState::Running(trace_writer) => {
TraceWriterState::Running(this.update_trace_writer(trace_writer)?)
}
TraceWriterState::Errored(e) => TraceWriterState::Errored(e),
})
});
}
self.state.setup_call(0);
self.state.run();
if self.made_initial_step {
self.read_traces::<false>();
} else {
self.read_traces::<true>();
}
self.made_initial_step = true;
self.needs_settle = false;
self.for_each_trace_writer_storing_error(|this, trace_writer_state| {
Ok(match trace_writer_state {
TraceWriterState::Decls(trace_writer_decls) => TraceWriterState::Running(
this.init_trace_writer(trace_writer_decls.write_decls(this.trace_decls)?)?,
),
TraceWriterState::Init(trace_writer) => {
TraceWriterState::Running(this.init_trace_writer(trace_writer)?)
}
TraceWriterState::Running(trace_writer) => {
TraceWriterState::Running(this.update_trace_writer(trace_writer)?)
}
TraceWriterState::Errored(e) => TraceWriterState::Errored(e),
})
});
panic!("settle(): took too many steps");
}
#[track_caller]
fn get_io(&self, target: Target) -> CompiledValue<CanonicalType> {
@ -3861,13 +4176,13 @@ impl SimulationImpl {
panic!("simulator read/write expression must not have dynamic array indexes");
}
#[track_caller]
fn read_bool_or_int<I: BoolOrIntType>(&mut self, io: Expr<I>) -> I::Value {
fn read_helper(&mut self, io: Expr<CanonicalType>) -> CompiledValue<CanonicalType> {
let Some(target) = io.target() else {
panic!("can't read from expression that's not a field/element of `Simulation::io()`");
};
let compiled_value = self.get_io(*target);
if self.made_initial_step {
self.settle_step();
self.settle();
} else {
match target.flow() {
Flow::Source => {
@ -3881,6 +4196,56 @@ impl SimulationImpl {
Flow::Duplex => unreachable!(),
}
}
compiled_value
}
#[track_caller]
fn write_helper(&mut self, io: Expr<CanonicalType>) -> CompiledValue<CanonicalType> {
let Some(target) = io.target() else {
panic!("can't write to an expression that's not a field/element of `Simulation::io()`");
};
let compiled_value = self.get_io(*target);
match target.flow() {
Flow::Source => {
panic!("can't write to an output");
}
Flow::Sink => {}
Flow::Duplex => unreachable!(),
}
if !self.made_initial_step {
self.uninitialized_inputs.remove(&*target);
}
self.needs_settle = true;
compiled_value
}
#[track_caller]
fn read_bit(&mut self, io: Expr<CanonicalType>) -> bool {
let compiled_value = self.read_helper(Expr::canonical(io));
match compiled_value.range.len() {
TypeLen::A_SMALL_SLOT => {
self.state.small_slots[compiled_value.range.small_slots.start] != 0
}
TypeLen::A_BIG_SLOT => !self.state.big_slots[compiled_value.range.big_slots.start]
.clone()
.is_zero(),
_ => unreachable!(),
}
}
#[track_caller]
fn write_bit(&mut self, io: Expr<CanonicalType>, value: bool) {
let compiled_value = self.write_helper(io);
match compiled_value.range.len() {
TypeLen::A_SMALL_SLOT => {
self.state.small_slots[compiled_value.range.small_slots.start] = value as _;
}
TypeLen::A_BIG_SLOT => {
self.state.big_slots[compiled_value.range.big_slots.start] = value.into()
}
_ => unreachable!(),
}
}
#[track_caller]
fn read_bool_or_int<I: BoolOrIntType>(&mut self, io: Expr<I>) -> I::Value {
let compiled_value = self.read_helper(Expr::canonical(io));
match compiled_value.range.len() {
TypeLen::A_SMALL_SLOT => Expr::ty(io).value_from_int_wrapping(
self.state.small_slots[compiled_value.range.small_slots.start],
@ -3893,22 +4258,8 @@ impl SimulationImpl {
}
#[track_caller]
fn write_bool_or_int<I: BoolOrIntType>(&mut self, io: Expr<I>, value: I::Value) {
let Some(target) = io.target() else {
panic!("can't write to an expression that's not a field/element of `Simulation::io()`");
};
let compiled_value = self.get_io(*target);
match target.flow() {
Flow::Source => {
panic!("can't write to an output");
}
Flow::Sink => {}
Flow::Duplex => unreachable!(),
}
let compiled_value = self.write_helper(Expr::canonical(io));
let value: BigInt = value.into();
if !self.made_initial_step {
self.uninitialized_inputs.remove(&*target);
}
self.needs_settle = true;
match compiled_value.range.len() {
TypeLen::A_SMALL_SLOT => {
self.state.small_slots[compiled_value.range.small_slots.start] =
@ -3985,13 +4336,13 @@ impl SimulationImpl {
}
fn close(mut self) -> std::io::Result<()> {
if self.made_initial_step {
self.settle_step();
self.settle();
}
self.close_all_trace_writers()
}
fn flush_traces(&mut self) -> std::io::Result<()> {
if self.made_initial_step {
self.settle_step();
self.settle();
}
self.for_each_trace_writer_getting_error(
|this, trace_writer: TraceWriterState<DynTraceWriterDecls>| match trace_writer {
@ -4082,6 +4433,7 @@ impl<T: BundleType> fmt::Debug for Simulation<T> {
traces,
trace_writers,
instant,
clocks_triggered,
},
io,
} = self;
@ -4099,6 +4451,7 @@ impl<T: BundleType> fmt::Debug for Simulation<T> {
.field("traces", traces)
.field("trace_writers", trace_writers)
.field("instant", instant)
.field("clocks_triggered", clocks_triggered)
.finish()
}
}
@ -4143,8 +4496,8 @@ impl<T: BundleType> Simulation<T> {
}
}
#[track_caller]
pub fn settle_step(&mut self) {
self.sim_impl.settle_step();
pub fn settle(&mut self) {
self.sim_impl.settle();
}
#[track_caller]
pub fn advance_time(&mut self, duration: SimDuration) {
@ -4168,4 +4521,28 @@ impl<T: BundleType> Simulation<T> {
self.sim_impl
.write_bool_or_int(io, I::bits_to_value(Cow::Borrowed(&value)));
}
#[track_caller]
pub fn write_clock(&mut self, io: Expr<Clock>, value: bool) {
self.sim_impl.write_bit(Expr::canonical(io), value);
}
#[track_caller]
pub fn read_clock(&mut self, io: Expr<Clock>) -> bool {
self.sim_impl.read_bit(Expr::canonical(io))
}
#[track_caller]
pub fn write_bool(&mut self, io: Expr<Bool>, value: bool) {
self.sim_impl.write_bit(Expr::canonical(io), value);
}
#[track_caller]
pub fn read_bool(&mut self, io: Expr<Bool>) -> bool {
self.sim_impl.read_bit(Expr::canonical(io))
}
#[track_caller]
pub fn write_reset<R: ResetType>(&mut self, io: Expr<R>, value: bool) {
self.sim_impl.write_bit(Expr::canonical(io), value);
}
#[track_caller]
pub fn read_reset<R: ResetType>(&mut self, io: Expr<R>) -> bool {
self.sim_impl.read_bit(Expr::canonical(io))
}
}

View file

@ -2201,6 +2201,17 @@ impl_insns! {
state.small_slots[dest] = value;
next!();
}
IsNonZeroDestIsSmall {
#[kind = Output]
dest: StatePartIndex<StatePartKindSmallSlots>,
#[kind = Input]
src: StatePartIndex<StatePartKindBigSlots>,
} => {
let src = &state.big_slots[src];
let value = !src.is_zero();
state.small_slots[dest] = value as SmallUInt;
next!();
}
CastToSInt {
#[kind = Output]
dest: StatePartIndex<StatePartKindBigSlots>,
@ -2237,6 +2248,18 @@ impl_insns! {
state.big_slots[dest] = value;
next!();
}
AndSmall {
#[kind = Output]
dest: StatePartIndex<StatePartKindSmallSlots>,
#[kind = Input]
lhs: StatePartIndex<StatePartKindSmallSlots>,
#[kind = Input]
rhs: StatePartIndex<StatePartKindSmallSlots>,
} => {
let value = state.small_slots[lhs] & state.small_slots[rhs];
state.small_slots[dest] = value;
next!();
}
Or {
#[kind = Output]
dest: StatePartIndex<StatePartKindBigSlots>,
@ -2283,6 +2306,16 @@ impl_insns! {
state.big_slots[dest] = value;
next!();
}
NotSmall {
#[kind = Output]
dest: StatePartIndex<StatePartKindSmallSlots>,
#[kind = Input]
src: StatePartIndex<StatePartKindSmallSlots>,
} => {
let value = !state.small_slots[src];
state.small_slots[dest] = value;
next!();
}
Neg {
#[kind = Output]
dest: StatePartIndex<StatePartKindBigSlots>,

File diff suppressed because it is too large Load diff