364 lines
12 KiB
Rust
364 lines
12 KiB
Rust
//! this example takes the multi_core_inverter but tiles the cores along their primary axis.
|
|
//! this allows for more loops between them, and thereby hopefully better coupling.
|
|
//!
|
|
//! to run this, from toplevel directory:
|
|
//! ```
|
|
//! $ cargo run --release --bin stacked_cores
|
|
//! $ pushd crates/coremem; cargo run --release --bin viewer ../../out/applications/stacked_cores/0/ ; popd
|
|
//! ```
|
|
|
|
use coremem::geom::{Coord as _, Meters};
|
|
use coremem::geom::region::{ElongatedTorus, Torus};
|
|
use coremem::mat::{Ferroxcube3R1MH, IsoConductorOr, IsomorphicConductor};
|
|
use coremem::meas;
|
|
#[allow(unused)]
|
|
use coremem::real::{self, Real as _};
|
|
use coremem::sim::spirv::{self, SpirvSim};
|
|
use coremem::sim::units::Seconds;
|
|
use coremem::stim::{CurlVectorField, Exp, Gated, ModulatedVectorField, Scaled, Shifted, TimeVaryingExt as _};
|
|
use coremem::Driver;
|
|
|
|
// type R = real::R32;
|
|
type R = f32;
|
|
type Mat = IsoConductorOr<R, Ferroxcube3R1MH>;
|
|
// type Backend = spirv::CpuBackend;
|
|
type Backend = spirv::WgpuBackend;
|
|
type Sim = SpirvSim::<R, Mat, Backend>;
|
|
|
|
|
|
|
|
enum DriveDirection {
|
|
High,
|
|
Low,
|
|
}
|
|
impl DriveDirection {
|
|
fn as_signed_f32(&self) -> f32 {
|
|
match self {
|
|
DriveDirection::High => 1.0,
|
|
DriveDirection::Low => -1.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// different states each control signal can be in for some clock cycle.
|
|
#[derive(Copy, Clone)]
|
|
enum ClockState {
|
|
HoldHigh,
|
|
ReleaseHigh,
|
|
HoldLow,
|
|
ReleaseLow,
|
|
Float,
|
|
}
|
|
type ClockSegment = Shifted<R, Gated<R, Scaled<Exp<R>, R>>>;
|
|
|
|
impl ClockState {
|
|
fn time_stimulus(&self, params: &Params, cycle: u32)
|
|
-> Option<ClockSegment>
|
|
{
|
|
use ClockState::*;
|
|
match self {
|
|
HoldHigh => Some(params.control_signal_hold(cycle, DriveDirection::High)),
|
|
ReleaseHigh => Some(params.control_signal_release(cycle, DriveDirection::High)),
|
|
HoldLow => Some(params.control_signal_hold(cycle, DriveDirection::Low)),
|
|
ReleaseLow => Some(params.control_signal_release(cycle, DriveDirection::Low)),
|
|
Float => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
struct Params {
|
|
input_magnitude: f32,
|
|
clock_phase_duration: f32,
|
|
clock_decay: f32, // exp decay half-life
|
|
ctl_conductivity: f32,
|
|
coupling_conductivity: f32,
|
|
// 's' = core (ferromagnetic part)
|
|
s_major: f32,
|
|
s_minor: f32,
|
|
// 'io' = drive/control wire
|
|
io_major: f32,
|
|
io_minor: f32,
|
|
coupling_length: f32,
|
|
coupling_major: f32,
|
|
coupling_minor: f32,
|
|
// coords for core 'n'
|
|
sx: f32,
|
|
sy: f32,
|
|
sz1: f32,
|
|
}
|
|
fn um(n: u32) -> f32 {
|
|
n as f32 * 1e-6
|
|
}
|
|
fn ps(n: u32) -> f32 {
|
|
n as f32 * 1e-12
|
|
}
|
|
impl Params {
|
|
fn sz(&self, n: u32) -> f32 {
|
|
(n + 1) as f32 * self.sz1
|
|
}
|
|
fn couplingz(&self, n: u32) -> f32 {
|
|
0.5 * (self.sz(n) + self.sz(n+1))
|
|
}
|
|
/// control loop for core n (alternately called "drive" loop)
|
|
fn ctl(&self, n: u32) -> Torus {
|
|
Torus::new_yz(Meters::new(self.sx - self.s_major, self.sy, self.sz(n)), self.io_major, self.io_minor)
|
|
}
|
|
/// the last core gets an external output in place of its coupling loop
|
|
fn sense(&self, n: u32) -> Torus {
|
|
Torus::new_xz(Meters::new(self.sx + self.s_major, self.sy, self.sz(n)), self.io_major, self.io_minor)
|
|
}
|
|
fn s(&self, n: u32) -> Torus {
|
|
Torus::new_xy(Meters::new(self.sx, self.sy, self.sz(n)), self.s_major, self.s_minor)
|
|
}
|
|
/// coupling(n) is the wire which couples core n into core n+1
|
|
fn coupling(&self, n: u32) -> ElongatedTorus {
|
|
// TODO: plumb an `angle` argument into this
|
|
ElongatedTorus::new_xz(
|
|
Meters::new(self.sx, self.sy, self.couplingz(n)),
|
|
self.coupling_length,
|
|
self.coupling_major,
|
|
self.coupling_minor,
|
|
)
|
|
}
|
|
|
|
fn control_signal_hold(&self, cycle: u32, ty: DriveDirection) -> ClockSegment {
|
|
// simple square wave:
|
|
let amp = ty.as_signed_f32() * self.input_magnitude;
|
|
let start = self.clock_phase_duration * cycle as f32;
|
|
Exp::new(100f32.cast() /* very long decay */)
|
|
.scaled(amp.cast())
|
|
.gated(R::zero(), self.clock_phase_duration.cast())
|
|
.shifted(start.cast())
|
|
}
|
|
|
|
fn control_signal_release(&self, cycle: u32, ty: DriveDirection) -> ClockSegment {
|
|
// decaying exponential wave:
|
|
let amp = ty.as_signed_f32() * self.input_magnitude;
|
|
let start = self.clock_phase_duration * cycle as f32;
|
|
Exp::new(self.clock_decay.cast() /* half life */)
|
|
.scaled(amp.cast())
|
|
.gated(R::zero(), self.clock_phase_duration.cast())
|
|
.shifted(start.cast())
|
|
}
|
|
|
|
fn with_input_magnitude(mut self, p: f32) -> Self {
|
|
self.input_magnitude = p;
|
|
self
|
|
}
|
|
fn with_clock_phase_duration(mut self, p: f32) -> Self {
|
|
self.clock_phase_duration = p;
|
|
self
|
|
}
|
|
fn with_clock_decay(mut self, p: f32) -> Self {
|
|
self.clock_decay = p;
|
|
self
|
|
}
|
|
fn with_ctl_conductivity(mut self, p: f32) -> Self {
|
|
self.ctl_conductivity = p;
|
|
self
|
|
}
|
|
fn with_coupling_conductivity(mut self, p: f32) -> Self {
|
|
self.coupling_conductivity = p;
|
|
self
|
|
}
|
|
}
|
|
|
|
|
|
/// minimal 2-core inverter.
|
|
/// analyze how the inverter transfers a zero v.s. a one.
|
|
#[allow(unused)]
|
|
fn drive_map_isolated_inv() -> [[ClockState; 2]; 6] {
|
|
use ClockState::*;
|
|
[
|
|
// charge each device to '1'
|
|
[HoldHigh, HoldHigh ],
|
|
// let the cores settle
|
|
[ReleaseHigh,ReleaseHigh],
|
|
// write S0 -> S1. S1 should be *cleared* to 0.
|
|
[HoldLow, Float ],
|
|
|
|
// charge S0=0, reset S1 for next write
|
|
[HoldLow, HoldHigh ],
|
|
// let the cores settle
|
|
[ReleaseLow, ReleaseHigh],
|
|
// write S0 -> S1. S1 should *keep its state* of 1.
|
|
[HoldLow, Float ],
|
|
]
|
|
}
|
|
|
|
fn main() {
|
|
coremem::init_logging();
|
|
// coremem::init_debug();
|
|
|
|
let params = Params {
|
|
input_magnitude: 0.0,
|
|
clock_phase_duration: 0.0,
|
|
clock_decay: 0.0,
|
|
ctl_conductivity: 0.0,
|
|
coupling_conductivity: 0.0,
|
|
// 's' = core (ferromagnetic part)
|
|
s_major: um(160),
|
|
s_minor: um(30),
|
|
// 'io' = drive/control wire
|
|
io_major: um(80),
|
|
io_minor: um(30),
|
|
coupling_length: um(320),
|
|
coupling_major: um(80),
|
|
coupling_minor: um(30),
|
|
// coords for core 'n'
|
|
sx: um(400),
|
|
sy: um(400),
|
|
sz1: um(320),
|
|
};
|
|
// TODO: use a deque, with push_front and push_back
|
|
let deferred = || {}; // add to this to schedule sims at a lower priority
|
|
run_sim(
|
|
"71-2ns-100ps-5e9A-1e3pctl-1e4pcpl",
|
|
drive_map_isolated_inv(),
|
|
params
|
|
.with_clock_phase_duration(ps(2000))
|
|
.with_clock_decay(ps(100))
|
|
.with_input_magnitude(5e9)
|
|
.with_ctl_conductivity(1e3)
|
|
.with_coupling_conductivity(1e4)
|
|
);
|
|
run_sim(
|
|
"76-2ns-100ps-1e10A-1e3pctl-1e4pcpl",
|
|
drive_map_isolated_inv(),
|
|
params
|
|
.with_clock_phase_duration(ps(2000))
|
|
.with_clock_decay(ps(100))
|
|
.with_input_magnitude(1e10)
|
|
.with_ctl_conductivity(1e3)
|
|
.with_coupling_conductivity(1e4)
|
|
);
|
|
run_sim(
|
|
"82-2ns-100ps-5e10A-1e3pctl-1e4pcpl",
|
|
drive_map_isolated_inv(),
|
|
params
|
|
.with_clock_phase_duration(ps(2000))
|
|
.with_clock_decay(ps(100))
|
|
.with_input_magnitude(5e10)
|
|
.with_ctl_conductivity(1e3)
|
|
.with_coupling_conductivity(1e4)
|
|
);
|
|
run_sim(
|
|
"85-2ns-100ps-1e11A-1e2pctl-1e4pcpl",
|
|
drive_map_isolated_inv(),
|
|
params
|
|
.with_clock_phase_duration(ps(2000))
|
|
.with_clock_decay(ps(100))
|
|
.with_input_magnitude(1e11)
|
|
.with_ctl_conductivity(1e2)
|
|
.with_coupling_conductivity(1e4)
|
|
);
|
|
|
|
deferred();
|
|
}
|
|
|
|
|
|
fn run_sim<const C: usize, const R: usize>(
|
|
name: &str, drive_map: [[ClockState; C]; R], params: Params
|
|
) {
|
|
// let ns = |n| n as f32 * 1e-9;
|
|
let feat_size = um(10);
|
|
|
|
let sim_padding = Meters::new(um(80), um(80), um(80));
|
|
let sim_bounds = |num_cores| {
|
|
Meters::new(params.sx * 2.0, params.sy * 2.0, params.sz(num_cores))
|
|
+ sim_padding
|
|
};
|
|
|
|
//////// define the control signals/transitions
|
|
// each row N denotes the drive currents at clock cycle N.
|
|
// each col M denotes the drive current at core M.
|
|
let num_cycles = drive_map.len() as u32;
|
|
let num_cores = drive_map[0].len() as u32;
|
|
let mut core_drivers = vec![Vec::default(); num_cores as usize];
|
|
for (cycle, cores) in drive_map.into_iter().enumerate() {
|
|
for (core, clock) in cores.into_iter().enumerate() {
|
|
core_drivers[core as usize].extend(clock.time_stimulus(¶ms, cycle as u32));
|
|
}
|
|
}
|
|
let stim: Vec<_> = core_drivers.into_iter()
|
|
.enumerate()
|
|
.map(|(core, time_varying)| {
|
|
let region = params.ctl(core as u32);
|
|
let area = region.cross_section();
|
|
let amp = 1.0 / area;
|
|
let v_field = CurlVectorField::new(region.clone());
|
|
ModulatedVectorField::new(v_field, time_varying.scaled(amp.cast::<R>()))
|
|
})
|
|
.collect();
|
|
assert_eq!(stim.len(), num_cores as usize);
|
|
|
|
|
|
let ctl_mat = IsomorphicConductor::new(params.ctl_conductivity.cast::<R>());
|
|
let coupling_mat = IsomorphicConductor::new(params.coupling_conductivity.cast::<R>());
|
|
let ferro_mat = Ferroxcube3R1MH::new();
|
|
|
|
let last_core = num_cores - 1;
|
|
|
|
let mut driver = Driver::new(Sim::new(
|
|
sim_bounds(num_cores).to_index(feat_size), feat_size,
|
|
));
|
|
driver.add_classical_boundary(sim_padding);
|
|
|
|
//////// create the wires and toroids
|
|
driver.fill_region(¶ms.sense(last_core), coupling_mat);
|
|
for core in 0..num_cores {
|
|
driver.fill_region(¶ms.s(core), ferro_mat);
|
|
driver.fill_region(¶ms.ctl(core), ctl_mat);
|
|
if core != last_core {
|
|
driver.fill_region(¶ms.coupling(core), coupling_mat);
|
|
}
|
|
}
|
|
|
|
//////// monitor some measurements
|
|
for core in 0..num_cores {
|
|
driver.add_measurement(meas::CurrentLoop::new(
|
|
&format!("drive{}", core),
|
|
params.ctl(core),
|
|
));
|
|
}
|
|
for core in 0..num_cores {
|
|
let name = format!("sense{}", core);
|
|
if core != last_core {
|
|
driver.add_measurement(meas::CurrentLoop::new(
|
|
&name, params.coupling(core),
|
|
));
|
|
} else {
|
|
driver.add_measurement(meas::CurrentLoop::new(
|
|
&name, params.sense(core),
|
|
));
|
|
}
|
|
}
|
|
for core in 0..num_cores {
|
|
driver.add_measurement(meas::MagneticLoop::new(
|
|
&format!("state{}", core),
|
|
params.s(core),
|
|
));
|
|
}
|
|
|
|
|
|
let duration = Seconds(params.clock_phase_duration * num_cycles as f32);
|
|
|
|
// let stim = DynStimuli::from_vec(stim.map(MapIntoBoxStimulus).into_vec());
|
|
let mut driver = driver.with_modulated_stimulus();
|
|
for s in stim {
|
|
driver.add_stimulus(s);
|
|
}
|
|
|
|
let prefix = format!("out/applications/stacked_cores/{}/", name);
|
|
let _ = std::fs::create_dir_all(&prefix);
|
|
driver.add_state_file(&*format!("{}state.bc", prefix), 25600);
|
|
// driver.add_serializer_renderer(&*format!("{}frame-", prefix), 6400, None);
|
|
// driver.add_csv_renderer(&*format!("{}meas-detailed.csv", prefix), 100, None);
|
|
driver.add_csv_renderer(&*format!("{}meas.csv", prefix), 1600, None);
|
|
driver.add_csv_renderer(&*format!("{}meas-sparse.csv", prefix), 12800, None);
|
|
driver.set_steps_per_stimulus(200);
|
|
|
|
driver.step_until(duration);
|
|
}
|