app: stacked_cores: prototype
the long term goal of this demo is to see if: (a) we can get stronger coupling between two cores than with multi-core-inverter. (b) we can get amplification by using a charge-pump like concept. (c) we can construct a *working* multi-core-inverter from this.
This commit is contained in:
parent
f737a4c916
commit
2044397047
|
@ -2093,6 +2093,13 @@ dependencies = [
|
|||
"coremem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stacked_cores"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"coremem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
|
|
|
@ -11,6 +11,7 @@ members = [
|
|||
"crates/applications/buffer_proto5",
|
||||
"crates/applications/multi_core_inverter",
|
||||
"crates/applications/sr_latch",
|
||||
"crates/applications/stacked_cores",
|
||||
"crates/applications/wavefront",
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "stacked_cores"
|
||||
version = "0.1.0"
|
||||
authors = ["Colin <colin@uninsane.org>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
coremem = { path = "../../coremem" }
|
|
@ -0,0 +1,363 @@
|
|||
//! 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);
|
||||
}
|
Loading…
Reference in New Issue