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:
colin 2022-09-01 18:40:20 -07:00
parent f737a4c916
commit 2044397047
4 changed files with 379 additions and 0 deletions

7
Cargo.lock generated
View File

@ -2093,6 +2093,13 @@ dependencies = [
"coremem",
]
[[package]]
name = "stacked_cores"
version = "0.1.0"
dependencies = [
"coremem",
]
[[package]]
name = "strsim"
version = "0.8.0"

View File

@ -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",
]

View File

@ -0,0 +1,8 @@
[package]
name = "stacked_cores"
version = "0.1.0"
authors = ["Colin <colin@uninsane.org>"]
edition = "2021"
[dependencies]
coremem = { path = "../../coremem" }

View File

@ -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(&params, 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(&params.sense(last_core), coupling_mat);
for core in 0..num_cores {
driver.fill_region(&params.s(core), ferro_mat);
driver.fill_region(&params.ctl(core), ctl_mat);
if core != last_core {
driver.fill_region(&params.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);
}