diff --git a/arbor/CMakeLists.txt b/arbor/CMakeLists.txt
index 923a4c97af3f8f117e3220f21f7c6dc8b40d8275..978d3a30514d3d189d0d51e5f0f944a0a4034fcf 100644
--- a/arbor/CMakeLists.txt
+++ b/arbor/CMakeLists.txt
@@ -10,6 +10,8 @@ set(arbor_sources
     communication/dry_run_context.cpp
     benchmark_cell_group.cpp
     builtin_mechanisms.cpp
+    cable_cell.cpp
+    cable_cell_param.cpp
     cell_group_factory.cpp
     common_types_io.cpp
     execution_context.cpp
@@ -22,7 +24,6 @@ set(arbor_sources
     io/locked_ostream.cpp
     io/serialize_hex.cpp
     lif_cell_group.cpp
-    cable_cell.cpp
     mc_cell_group.cpp
     mechcat.cpp
     memory/cuda_wrappers.cpp
diff --git a/arbor/backends/gpu/shared_state.cpp b/arbor/backends/gpu/shared_state.cpp
index 5c29d9fb31dafc5989c376651d0cd83d699bdfdc..e15692beb7962f6f06a534cbc6ad26bbcf03d1bb 100644
--- a/arbor/backends/gpu/shared_state.cpp
+++ b/arbor/backends/gpu/shared_state.cpp
@@ -3,12 +3,12 @@
 
 #include <arbor/constants.hpp>
 #include <arbor/fvm_types.hpp>
-#include <arbor/ion_info.hpp>
 
 #include "backends/event.hpp"
 #include "backends/gpu/gpu_store_types.hpp"
 #include "backends/gpu/shared_state.hpp"
 #include "backends/multi_event_stream_state.hpp"
+#include "memory/copy.hpp"
 #include "memory/wrappers.hpp"
 #include "util/rangeutil.hpp"
 
@@ -19,14 +19,6 @@ namespace gpu {
 
 // CUDA implementation entry points:
 
-void init_concentration_impl(
-    std::size_t n, fvm_value_type* Xi, fvm_value_type* Xo, const fvm_value_type* weight_Xi,
-    const fvm_value_type* weight_Xo, fvm_value_type iconc, fvm_value_type econc);
-
-void nernst_impl(
-    std::size_t n, fvm_value_type factor,
-    const fvm_value_type* charge, const fvm_value_type* Xi, const fvm_value_type* Xo, fvm_value_type* eX);
-
 void update_time_to_impl(
     std::size_t n, fvm_value_type* time_to, const fvm_value_type* time,
     fvm_value_type dt, fvm_value_type tmax);
@@ -46,6 +38,8 @@ void take_samples_impl(
     const multi_event_stream_state<raw_probe_info>& s,
     const fvm_value_type* time, fvm_value_type* sample_time, fvm_value_type* sample_value);
 
+void add_scalar(std::size_t n, fvm_value_type* data, fvm_value_type v);
+
 // GPU-side minmax: consider CUDA kernel replacement.
 std::pair<fvm_value_type, fvm_value_type> minmax_value_impl(fvm_size_type n, const fvm_value_type* v) {
     auto v_copy = memory::on_host(memory::const_device_view<fvm_value_type>(v, n));
@@ -55,10 +49,11 @@ std::pair<fvm_value_type, fvm_value_type> minmax_value_impl(fvm_size_type n, con
 // Ion state methods:
 
 ion_state::ion_state(
-    ion_info info,
+    int charge,
     const std::vector<fvm_index_type>& cv,
-    const std::vector<fvm_value_type>& iconc_norm_area,
-    const std::vector<fvm_value_type>& econc_norm_area,
+    const std::vector<fvm_value_type>& init_iconc,
+    const std::vector<fvm_value_type>& init_econc,
+    const std::vector<fvm_value_type>& init_erev,
     unsigned // alignment/padding ignored.
 ):
     node_index_(make_const_view(cv)),
@@ -66,53 +61,39 @@ ion_state::ion_state(
     eX_(cv.size(), NAN),
     Xi_(cv.size(), NAN),
     Xo_(cv.size(), NAN),
-    weight_Xi_(make_const_view(iconc_norm_area)),
-    weight_Xo_(make_const_view(econc_norm_area)),
-    charge(1u, info.charge),
-    default_int_concentration(info.default_int_concentration),
-    default_ext_concentration(info.default_ext_concentration)
+    init_Xi_(make_const_view(init_iconc)),
+    init_Xo_(make_const_view(init_econc)),
+    init_eX_(make_const_view(init_erev)),
+    charge(1u, charge)
 {
-    arb_assert(node_index_.size()==weight_Xi_.size());
-    arb_assert(node_index_.size()==weight_Xo_.size());
-}
-
-void ion_state::nernst(fvm_value_type temperature_K) {
-    // Nernst equation: reversal potenial eX given by:
-    //
-    //     eX = RT/zF * ln(Xo/Xi)
-    //
-    // where:
-    //     R: universal gas constant 8.3144598 J.K-1.mol-1
-    //     T: temperature in Kelvin
-    //     z: valency of species (K, Na: +1) (Ca: +2)
-    //     F: Faraday's constant 96485.33289 C.mol-1
-    //     Xo/Xi: ratio of out/in concentrations
-
-    // 1e3 factor required to scale from V -> mV.
-    constexpr fvm_value_type RF = 1e3*constant::gas_constant/constant::faraday;
-
-    fvm_value_type factor = RF*temperature_K;
-    nernst_impl(Xi_.size(), factor, charge.data(), Xo_.data(), Xi_.data(), eX_.data());
+    arb_assert(node_index_.size()==init_Xi_.size());
+    arb_assert(node_index_.size()==init_Xo_.size());
+    arb_assert(node_index_.size()==init_eX_.size());
 }
 
 void ion_state::init_concentration() {
-    init_concentration_impl(
-        Xi_.size(),
-        Xi_.data(), Xo_.data(),
-        weight_Xi_.data(), weight_Xo_.data(),
-        default_int_concentration, default_ext_concentration);
+    memory::copy(init_Xi_, Xi_);
+    memory::copy(init_Xo_, Xo_);
 }
 
 void ion_state::zero_current() {
     memory::fill(iX_, 0);
 }
 
+void ion_state::reset() {
+    zero_current();
+    init_concentration();
+    memory::copy(init_eX_, eX_);
+}
+
 // Shared state methods:
 
 shared_state::shared_state(
     fvm_size_type n_intdom,
     const std::vector<fvm_index_type>& cv_to_intdom_vec,
     const std::vector<fvm_gap_junction>& gj_vec,
+    const std::vector<fvm_value_type>& init_membrane_potential,
+    const std::vector<fvm_value_type>& temperature_K,
     unsigned // alignment parameter ignored.
 ):
     n_intdom(n_intdom),
@@ -127,32 +108,35 @@ shared_state::shared_state(
     voltage(n_cv),
     current_density(n_cv),
     conductivity(n_cv),
-    temperature_degC(1),
+    init_voltage(make_const_view(init_membrane_potential)),
+    temperature_degC(make_const_view(temperature_K)),
     deliverable_events(n_intdom)
-{}
+{
+    add_scalar(temperature_degC.size(), temperature_degC.data(), -273.15);
+}
 
 void shared_state::add_ion(
     const std::string& ion_name,
-    ion_info info,
+    int charge,
     const std::vector<fvm_index_type>& cv,
-    const std::vector<fvm_value_type>& iconc_norm_area,
-    const std::vector<fvm_value_type>& econc_norm_area)
+    const std::vector<fvm_value_type>& init_iconc,
+    const std::vector<fvm_value_type>& init_econc,
+    const std::vector<fvm_value_type>& init_erev)
 {
     ion_data.emplace(std::piecewise_construct,
         std::forward_as_tuple(ion_name),
-        std::forward_as_tuple(info, cv, iconc_norm_area, econc_norm_area, 1u));
+        std::forward_as_tuple(charge, cv, init_iconc, init_econc, init_erev, 1u));
 }
 
-void shared_state::reset(fvm_value_type initial_voltage, fvm_value_type temperature_K) {
-    memory::fill(voltage, initial_voltage);
+void shared_state::reset() {
+    memory::copy(init_voltage, voltage);
     memory::fill(current_density, 0);
     memory::fill(conductivity, 0);
     memory::fill(time, 0);
     memory::fill(time_to, 0);
-    memory::fill(temperature_degC, temperature_K - 273.15);
 
     for (auto& i: ion_data) {
-        i.second.reset(temperature_K);
+        i.second.reset();
     }
 }
 
@@ -170,12 +154,6 @@ void shared_state::ions_init_concentration() {
     }
 }
 
-void shared_state::ions_nernst_reversal_potential(fvm_value_type temperature_K) {
-    for (auto& i: ion_data) {
-        i.second.nernst(temperature_K);
-    }
-}
-
 void shared_state::update_time_to(fvm_value_type dt_step, fvm_value_type tmax) {
     update_time_to_impl(n_intdom, time_to.data(), time.data(), dt_step, tmax);
 }
@@ -203,21 +181,26 @@ void shared_state::take_samples(const sample_event_stream::state& s, array& samp
 // Debug interface
 std::ostream& operator<<(std::ostream& o, shared_state& s) {
     o << " cv_to_intdom " << s.cv_to_intdom << "\n";
-    o << " time       " << s.time << "\n";
-    o << " time_to    " << s.time_to << "\n";
+    o << " time         " << s.time << "\n";
+    o << " time_to      " << s.time_to << "\n";
     o << " dt_intdom    " << s.dt_intdom << "\n";
-    o << " dt_cv      " << s.dt_cv << "\n";
-    o << " voltage    " << s.voltage << "\n";
-    o << " current    " << s.current_density << "\n";
+    o << " dt_cv        " << s.dt_cv << "\n";
+    o << " voltage      " << s.voltage << "\n";
+    o << " init_voltage " << s.init_voltage << "\n";
+    o << " temperature  " << s.temperature_degC << "\n";
+    o << " current      " << s.current_density << "\n";
     o << " conductivity " << s.conductivity << "\n";
     for (auto& ki: s.ion_data) {
         auto& kn = ki.first;
         auto& i = const_cast<ion_state&>(ki.second);
-        o << " " << kn << ".current_density        " << i.iX_ << "\n";
-        o << " " << kn << ".reversal_potential     " << i.eX_ << "\n";
-        o << " " << kn << ".internal_concentration " << i.Xi_ << "\n";
-        o << " " << kn << ".external_concentration " << i.Xo_ << "\n";
-        o << " " << kn << ".node_index             " << i.node_index_ << "\n";
+        o << " " << kn << "/current_density        " << i.iX_ << "\n";
+        o << " " << kn << "/reversal_potential     " << i.eX_ << "\n";
+        o << " " << kn << "/internal_concentration " << i.Xi_ << "\n";
+        o << " " << kn << "/external_concentration " << i.Xo_ << "\n";
+        o << " " << kn << "/intconc_initial        " << i.init_Xi_ << "\n";
+        o << " " << kn << "/extconc_initial        " << i.init_Xo_ << "\n";
+        o << " " << kn << "/revpot_initial         " << i.init_eX_ << "\n";
+        o << " " << kn << "/node_index             " << i.node_index_ << "\n";
     }
     return o;
 }
diff --git a/arbor/backends/gpu/shared_state.cu b/arbor/backends/gpu/shared_state.cu
index 34912740283b8fa07764c257b0a5b32f9898bc44..78c8a0919147c1789e873fb696c1cb59265cada9 100644
--- a/arbor/backends/gpu/shared_state.cu
+++ b/arbor/backends/gpu/shared_state.cu
@@ -13,27 +13,6 @@ namespace gpu {
 
 namespace kernel {
 
-template <typename T>
-__global__
-void nernst_impl(unsigned n, T factor, const T* charge, const T* Xo, const T* Xi, T* eX) {
-    unsigned i = threadIdx.x+blockIdx.x*blockDim.x;
-
-    if (i<n) {
-        eX[i] = factor*std::log(Xo[i]/Xi[i])/charge[0];
-    }
-}
-
-template <typename T>
-__global__
-void init_concentration_impl(unsigned n, T* Xi, T* Xo, const T* weight_Xi, const T* weight_Xo, T c_int, T c_ext) {
-    unsigned i = threadIdx.x+blockIdx.x*blockDim.x;
-
-    if (i<n) {
-        Xi[i] = c_int*weight_Xi[i];
-        Xo[i] = c_ext*weight_Xo[i];
-    }
-}
-
 template <typename T>
 __global__ void update_time_to_impl(unsigned n, T* time_to, const T* time, T dt, T tmax) {
     unsigned i = threadIdx.x+blockIdx.x*blockDim.x;
@@ -54,6 +33,15 @@ __global__ void add_gj_current_impl(unsigned n, const T* gj_info, const I* volta
     }
 }
 
+// Vector/scalar addition: x[i] += v ∀i
+template <typename T>
+__global__ void add_scalar(unsigned n, T* x, fvm_value_type v) {
+    unsigned i = threadIdx.x+blockIdx.x*blockDim.x;
+    if (i<n) {
+        x[i] += v;
+    }
+}
+
 // Vector minus: x = y - z
 template <typename T>
 __global__ void vec_minus(unsigned n, T* x, const T* y, const T* z) {
@@ -91,26 +79,12 @@ __global__ void take_samples_impl(
 
 using impl::block_count;
 
-void nernst_impl(
-    std::size_t n, fvm_value_type factor,
-    const fvm_value_type* charge, const fvm_value_type* Xo, const fvm_value_type* Xi, fvm_value_type* eX)
-{
-    if (!n) return;
-
-    constexpr int block_dim = 128;
-    int nblock = block_count(n, block_dim);
-    kernel::nernst_impl<<<nblock, block_dim>>>(n, factor, charge, Xo, Xi, eX);
-}
-
-void init_concentration_impl(
-    std::size_t n, fvm_value_type* Xi, fvm_value_type* Xo, const fvm_value_type* weight_Xi,
-    const fvm_value_type* weight_Xo, fvm_value_type c_int, fvm_value_type c_ext)
-{
+void add_scalar(std::size_t n, fvm_value_type* data, fvm_value_type v) {
     if (!n) return;
 
     constexpr int block_dim = 128;
-    int nblock = block_count(n, block_dim);
-    kernel::init_concentration_impl<<<nblock, block_dim>>>(n, Xi, Xo, weight_Xi, weight_Xo, c_int, c_ext);
+    const int nblock = block_count(n, block_dim);
+    kernel::add_scalar<<<nblock, block_dim>>>(n, data, v);
 }
 
 void update_time_to_impl(
diff --git a/arbor/backends/gpu/shared_state.hpp b/arbor/backends/gpu/shared_state.hpp
index 6aea214e9352a13edeb80622f2bbe45c44265b7e..a2d99456a8bdbc007899f4027384735c0802cb31 100644
--- a/arbor/backends/gpu/shared_state.hpp
+++ b/arbor/backends/gpu/shared_state.hpp
@@ -6,7 +6,6 @@
 #include <vector>
 
 #include <arbor/fvm_types.hpp>
-#include <arbor/ion_info.hpp>
 
 #include "backends/gpu/gpu_store_types.hpp"
 
@@ -31,37 +30,33 @@ struct ion_state {
     array eX_;          // (mV) reversal potential
     array Xi_;          // (mM) internal concentration
     array Xo_;          // (mM) external concentration
-    array weight_Xi_;   // (1) concentration weight internal
-    array weight_Xo_;   // (1) concentration weight external
+
+    array init_Xi_;     // (mM) area-weighted initial internal concentration
+    array init_Xo_;     // (mM) area-weighted initial external concentration
+    array init_eX_;     // (mM) initial reversal potential
 
     array charge;       // charge of ionic species (global, length 1)
-    fvm_value_type default_int_concentration; // (mM) default internal concentration
-    fvm_value_type default_ext_concentration; // (mM) default external concentration
 
     ion_state() = default;
 
     ion_state(
-        ion_info info,
+        int charge,
         const std::vector<fvm_index_type>& cv,
-        const std::vector<fvm_value_type>& iconc_norm_area,
-        const std::vector<fvm_value_type>& econc_norm_area,
+        const std::vector<fvm_value_type>& init_Xi,
+        const std::vector<fvm_value_type>& init_Xo,
+        const std::vector<fvm_value_type>& init_eX,
         unsigned align
     );
 
-    // Calculate the reversal potential eX (mV) using Nernst equation
-    void nernst(fvm_value_type temperature_K);
-
     // Set ion concentrations to weighted proportion of default concentrations.
     void init_concentration();
 
     // Set ionic current density to zero.
     void zero_current();
 
-    void reset(fvm_value_type temperature_K) {
-        zero_current();
-        init_concentration();
-        nernst(temperature_K);
-    }
+    // Zero currents, reset concentrations, and reset reversal potential from
+    // initial values.
+    void reset();
 };
 
 struct shared_state {
@@ -78,7 +73,9 @@ struct shared_state {
     array voltage;           // Maps CV index to membrane voltage [mV].
     array current_density;   // Maps CV index to current density [A/m²].
     array conductivity;      // Maps CV index to membrane conductivity [kS/m²].
-    array temperature_degC;  // Global temperature [°C] (length 1 array).
+
+    array init_voltage;      // Maps CV index to initial membrane voltage [mV].
+    array temperature_degC;  // Maps CV to local temperature (read only) [°C].
 
     std::unordered_map<std::string, ion_state> ion_data;
 
@@ -90,22 +87,23 @@ struct shared_state {
         fvm_size_type n_intdom,
         const std::vector<fvm_index_type>& cv_to_intdom_vec,
         const std::vector<fvm_gap_junction>& gj_vec,
+        const std::vector<fvm_value_type>& init_membrane_potential,
+        const std::vector<fvm_value_type>& temperature_K,
         unsigned align
     );
 
     void add_ion(
         const std::string& ion_name,
-        ion_info info,
+        int charge,
         const std::vector<fvm_index_type>& cv,
-        const std::vector<fvm_value_type>& iconc_norm_area,
-        const std::vector<fvm_value_type>& econc_norm_area);
+        const std::vector<fvm_value_type>& init_iconc,
+        const std::vector<fvm_value_type>& init_econc,
+        const std::vector<fvm_value_type>& init_erev);
 
     void zero_currents();
 
     void ions_init_concentration();
 
-    void ions_nernst_reversal_potential(fvm_value_type temperature_K);
-
     // Set time_to to earliest of time+dt_step and tmax.
     void update_time_to(fvm_value_type dt_step, fvm_value_type tmax);
 
@@ -128,7 +126,7 @@ struct shared_state {
         array& sample_time,
         array& sample_value);
 
-    void reset(fvm_value_type initial_voltage, fvm_value_type temperature_K);
+    void reset();
 };
 
 // For debugging only
diff --git a/arbor/backends/multicore/mechanism.cpp b/arbor/backends/multicore/mechanism.cpp
index 117fdc25719bf66e288fe4d95f7f00a33dacbaed..5c62c1c3df4dba1ef6f05b80b7cbfb9ae083ff0f 100644
--- a/arbor/backends/multicore/mechanism.cpp
+++ b/arbor/backends/multicore/mechanism.cpp
@@ -93,7 +93,7 @@ void mechanism::instantiate(unsigned id, backend::shared_state& shared, const me
     vec_i_    = shared.current_density.data();
     vec_g_    = shared.conductivity.data();
 
-    temperature_degC_ = &shared.temperature_degC;
+    temperature_degC_ = shared.temperature_degC.data();
 
     auto ion_state_tbl = ion_state_table();
     n_ion_ = ion_state_tbl.size();
diff --git a/arbor/backends/multicore/shared_state.cpp b/arbor/backends/multicore/shared_state.cpp
index 73c6605569e57dd08f6a20eaa47bc03052e3dad1..84986e53d14c33cc6590ef71746213c2c2b78bce 100644
--- a/arbor/backends/multicore/shared_state.cpp
+++ b/arbor/backends/multicore/shared_state.cpp
@@ -1,3 +1,4 @@
+#include <algorithm>
 #include <cfloat>
 #include <cmath>
 #include <iostream>
@@ -10,7 +11,6 @@
 #include <arbor/common_types.hpp>
 #include <arbor/constants.hpp>
 #include <arbor/fvm_types.hpp>
-#include <arbor/ion_info.hpp>
 #include <arbor/math.hpp>
 #include <arbor/simd/simd.hpp>
 
@@ -47,70 +47,44 @@ using pad = util::padded_allocator<>;
 // ion_state methods:
 
 ion_state::ion_state(
-    ion_info info,
+    int charge,
     const std::vector<fvm_index_type>& cv,
-    const std::vector<fvm_value_type>& iconc_norm_area,
-    const std::vector<fvm_value_type>& econc_norm_area,
+    const std::vector<fvm_value_type>& init_Xi,
+    const std::vector<fvm_value_type>& init_Xo,
+    const std::vector<fvm_value_type>& init_eX,
     unsigned align
 ):
     alignment(min_alignment(align)),
     node_index_(cv.begin(), cv.end(), pad(alignment)),
     iX_(cv.size(), NAN, pad(alignment)),
-    eX_(cv.size(), NAN, pad(alignment)),
+    eX_(init_eX.begin(), init_eX.end(), pad(alignment)),
     Xi_(cv.size(), NAN, pad(alignment)),
     Xo_(cv.size(), NAN, pad(alignment)),
-    weight_Xi_(iconc_norm_area.begin(), iconc_norm_area.end(), pad(alignment)),
-    weight_Xo_(econc_norm_area.begin(), econc_norm_area.end(), pad(alignment)),
-    charge(1u, info.charge, pad(alignment)),
-    default_int_concentration(info.default_int_concentration),
-    default_ext_concentration(info.default_ext_concentration)
+    init_Xi_(init_Xi.begin(), init_Xi.end(), pad(alignment)),
+    init_Xo_(init_Xo.begin(), init_Xo.end(), pad(alignment)),
+    init_eX_(init_eX.begin(), init_eX.end(), pad(alignment)),
+    charge(1u, charge, pad(alignment))
 {
-    arb_assert(node_index_.size()==weight_Xi_.size());
-    arb_assert(node_index_.size()==weight_Xo_.size());
-}
-
-void ion_state::nernst(fvm_value_type temperature_K) {
-    // Nernst equation: reversal potenial eX given by:
-    //
-    //     eX = RT/zF * ln(Xo/Xi)
-    //
-    // where:
-    //     R: universal gas constant 8.3144598 J.K-1.mol-1
-    //     T: temperature in Kelvin
-    //     z: valency of species (K, Na: +1) (Ca: +2)
-    //     F: Faraday's constant 96485.33289 C.mol-1
-    //     Xo/Xi: ratio of out/in concentrations
-
-    // 1e3 factor required to scale from V -> mV.
-    constexpr fvm_value_type RF = 1e3*constant::gas_constant/constant::faraday;
-
-    simd_value_type factor = RF*temperature_K/charge[0];
-    for (std::size_t i=0; i<Xi_.size(); i+=simd_width) {
-        simd_value_type xi(Xi_.data()+i);
-        simd_value_type xo(Xo_.data()+i);
-
-        auto ex = factor*log(xo/xi);
-        ex.copy_to(eX_.data()+i);
-    }
+    arb_assert(node_index_.size()==init_Xi_.size());
+    arb_assert(node_index_.size()==init_Xo_.size());
+    arb_assert(node_index_.size()==eX_.size());
+    arb_assert(node_index_.size()==init_eX_.size());
 }
 
 void ion_state::init_concentration() {
-    for (std::size_t i=0u; i<Xi_.size(); i+=simd_width) {
-        simd_value_type weight_xi(weight_Xi_.data()+i);
-        simd_value_type weight_xo(weight_Xo_.data()+i);
-
-        auto xi = default_int_concentration*weight_xi;
-        xi.copy_to(Xi_.data()+i);
-
-        auto xo = default_ext_concentration*weight_xo;
-        xo.copy_to(Xo_.data()+i);
-    }
+    std::copy(init_Xi_.begin(), init_Xi_.end(), Xi_.begin());
+    std::copy(init_Xo_.begin(), init_Xo_.end(), Xo_.begin());
 }
 
 void ion_state::zero_current() {
     util::fill(iX_, 0);
 }
 
+void ion_state::reset() {
+    zero_current();
+    init_concentration();
+    std::copy(init_eX_.begin(), init_eX_.end(), eX_.begin());
+}
 
 // shared_state methods:
 
@@ -118,6 +92,8 @@ shared_state::shared_state(
     fvm_size_type n_intdom,
     const std::vector<fvm_index_type>& cv_to_intdom_vec,
     const std::vector<fvm_gap_junction>& gj_vec,
+    const std::vector<fvm_value_type>& init_membrane_potential,
+    const std::vector<fvm_value_type>& temperature_K,
     unsigned align
 ):
     alignment(min_alignment(align)),
@@ -134,7 +110,8 @@ shared_state::shared_state(
     voltage(n_cv, pad(alignment)),
     current_density(n_cv, pad(alignment)),
     conductivity(n_cv, pad(alignment)),
-    temperature_degC(NAN),
+    init_voltage(init_membrane_potential.begin(), init_membrane_potential.end(), pad(alignment)),
+    temperature_degC(n_cv, pad(alignment)),
     deliverable_events(n_intdom)
 {
     // For indices in the padded tail of cv_to_intdom, set index to last valid intdom index.
@@ -146,30 +123,34 @@ shared_state::shared_state(
         std::copy(gj_vec.begin(), gj_vec.end(), gap_junctions.begin());
         std::fill(gap_junctions.begin()+n_gj, gap_junctions.end(), gj_vec.back());
     }
+
+    for (unsigned i = 0; i<n_cv; ++i) {
+        temperature_degC[i] = temperature_K[i] - 273.15;
+    }
 }
 
 void shared_state::add_ion(
     const std::string& ion_name,
-    ion_info info,
+    int charge,
     const std::vector<fvm_index_type>& cv,
-    const std::vector<fvm_value_type>& iconc_norm_area,
-    const std::vector<fvm_value_type>& econc_norm_area)
+    const std::vector<fvm_value_type>& init_iconc,
+    const std::vector<fvm_value_type>& init_econc,
+    const std::vector<fvm_value_type>& init_erev)
 {
     ion_data.emplace(std::piecewise_construct,
         std::forward_as_tuple(ion_name),
-        std::forward_as_tuple(info, cv, iconc_norm_area, econc_norm_area, alignment));
+        std::forward_as_tuple(charge, cv, init_iconc, init_econc, init_erev, alignment));
 }
 
-void shared_state::reset(fvm_value_type initial_voltage, fvm_value_type temperature_K) {
-    util::fill(voltage, initial_voltage);
+void shared_state::reset() {
+    std::copy(init_voltage.begin(), init_voltage.end(), voltage.begin());
     util::fill(current_density, 0);
     util::fill(conductivity, 0);
     util::fill(time, 0);
     util::fill(time_to, 0);
-    temperature_degC = temperature_K - 273.15;
 
     for (auto& i: ion_data) {
-        i.second.reset(temperature_K);
+        i.second.reset();
     }
 }
 
@@ -187,12 +168,6 @@ void shared_state::ions_init_concentration() {
     }
 }
 
-void shared_state::ions_nernst_reversal_potential(fvm_value_type temperature_K) {
-    for (auto& i: ion_data) {
-        i.second.nernst(temperature_K);
-    }
-}
-
 void shared_state::update_time_to(fvm_value_type dt_step, fvm_value_type tmax) {
     for (fvm_size_type i = 0; i<n_intdom; i+=simd_width) {
         simd_value_type t(time.data()+i);
@@ -258,23 +233,28 @@ std::ostream& operator<<(std::ostream& out, const shared_state& s) {
     using io::csv;
 
     out << "n_intdom     " << s.n_intdom << "\n";
-    out << "n_cv       " << s.n_cv << "\n";
+    out << "n_cv         " << s.n_cv << "\n";
     out << "cv_to_intdom " << csv(s.cv_to_intdom) << "\n";
-    out << "time       " << csv(s.time) << "\n";
-    out << "time_to    " << csv(s.time_to) << "\n";
+    out << "time         " << csv(s.time) << "\n";
+    out << "time_to      " << csv(s.time_to) << "\n";
     out << "dt_intdom    " << csv(s.dt_intdom) << "\n";
-    out << "dt_cv      " << csv(s.dt_cv) << "\n";
-    out << "voltage    " << csv(s.voltage) << "\n";
-    out << "current    " << csv(s.current_density) << "\n";
+    out << "dt_cv        " << csv(s.dt_cv) << "\n";
+    out << "voltage      " << csv(s.voltage) << "\n";
+    out << "init_voltage " << csv(s.init_voltage) << "\n";
+    out << "temperature  " << csv(s.temperature_degC) << "\n";
+    out << "current      " << csv(s.current_density) << "\n";
     out << "conductivity " << csv(s.conductivity) << "\n";
     for (const auto& ki: s.ion_data) {
         auto& kn = ki.first;
         auto& i = const_cast<ion_state&>(ki.second);
-        out << kn << ".current_density        " << csv(i.iX_) << "\n";
-        out << kn << ".reversal_potential     " << csv(i.eX_) << "\n";
-        out << kn << ".internal_concentration " << csv(i.Xi_) << "\n";
-        out << kn << ".external_concentration " << csv(i.Xo_) << "\n";
-        out << kn << ".node_index             " << csv(i.node_index_) << "\n";
+        out << kn << "/current_density        " << csv(i.iX_) << "\n";
+        out << kn << "/reversal_potential     " << csv(i.eX_) << "\n";
+        out << kn << "/internal_concentration " << csv(i.Xi_) << "\n";
+        out << kn << "/external_concentration " << csv(i.Xo_) << "\n";
+        out << kn << "/intconc_initial        " << csv(i.init_Xi_) << "\n";
+        out << kn << "/extconc_initial        " << csv(i.init_Xo_) << "\n";
+        out << kn << "/revpot_initial         " << csv(i.init_eX_) << "\n";
+        out << kn << "/node_index             " << csv(i.node_index_) << "\n";
     }
 
     return out;
diff --git a/arbor/backends/multicore/shared_state.hpp b/arbor/backends/multicore/shared_state.hpp
index 4d97d1cd0d3d3350c4e9ab8e449fca8b91b3eb5a..3a16c03a8ddaec0b8cae780080c1d5b3146eceac 100644
--- a/arbor/backends/multicore/shared_state.hpp
+++ b/arbor/backends/multicore/shared_state.hpp
@@ -10,7 +10,6 @@
 #include <arbor/assert.hpp>
 #include <arbor/common_types.hpp>
 #include <arbor/fvm_types.hpp>
-#include <arbor/ion_info.hpp>
 #include <arbor/simd/simd.hpp>
 
 #include "backends/event.hpp"
@@ -46,37 +45,33 @@ struct ion_state {
     array eX_;              // (mV) reversal potential
     array Xi_;              // (mM) internal concentration
     array Xo_;              // (mM) external concentration
-    array weight_Xi_;       // (1) concentration weight internal
-    array weight_Xo_;       // (1) concentration weight external
+
+    array init_Xi_;         // (mM) area-weighted initial internal concentration
+    array init_Xo_;         // (mM) area-weighted initial external concentration
+    array init_eX_;         // (mV) initial reversal potential
 
     array charge;           // charge of ionic species (global value, length 1)
-    fvm_value_type default_int_concentration; // (mM) default internal concentration
-    fvm_value_type default_ext_concentration; // (mM) default external concentration
 
     ion_state() = default;
 
     ion_state(
-        ion_info info,
+        int charge,
         const std::vector<fvm_index_type>& cv,
-        const std::vector<fvm_value_type>& iconc_norm_area,
-        const std::vector<fvm_value_type>& econc_norm_area,
+        const std::vector<fvm_value_type>& init_Xi,
+        const std::vector<fvm_value_type>& init_Xo,
+        const std::vector<fvm_value_type>& init_eX,
         unsigned align
     );
 
-    // Calculate the reversal potential eX (mV) using Nernst equation
-    void nernst(fvm_value_type temperature_K);
-
     // Set ion concentrations to weighted proportion of default concentrations.
     void init_concentration();
 
     // Set ionic current density to zero.
     void zero_current();
 
-    void reset(fvm_value_type temperature_K) {
-        zero_current();
-        init_concentration();
-        nernst(temperature_K);
-    }
+    // Zero currents, reset concentrations, and reset reversal potential from
+    // initial values.
+    void reset();
 };
 
 struct shared_state {
@@ -96,7 +91,9 @@ struct shared_state {
     array voltage;            // Maps CV index to membrane voltage [mV].
     array current_density;    // Maps CV index to membrane current density contributions [A/m²].
     array conductivity;       // Maps CV index to membrane conductivity [kS/m²].
-    fvm_value_type temperature_degC;  // Global temperature [°C].
+
+    array init_voltage;       // Maps CV index to initial membrane voltage [mV].
+    array temperature_degC;   // Maps CV to local temperature (read only) [°C].
 
     std::unordered_map<std::string, ion_state> ion_data;
 
@@ -108,15 +105,18 @@ struct shared_state {
         fvm_size_type n_intdom,
         const std::vector<fvm_index_type>& cv_to_intdom_vec,
         const std::vector<fvm_gap_junction>& gj_vec,
+        const std::vector<fvm_value_type>& init_membrane_potential,
+        const std::vector<fvm_value_type>& temperature_K,
         unsigned align
     );
 
     void add_ion(
         const std::string& ion_name,
-        ion_info info,
+        int charge,
         const std::vector<fvm_index_type>& cv,
-        const std::vector<fvm_value_type>& iconc_norm_area,
-        const std::vector<fvm_value_type>& econc_norm_area);
+        const std::vector<fvm_value_type>& init_iconc,
+        const std::vector<fvm_value_type>& init_econc,
+        const std::vector<fvm_value_type>& init_erev);
 
     void zero_currents();
 
@@ -146,7 +146,7 @@ struct shared_state {
         array& sample_time,
         array& sample_value);
 
-    void reset(fvm_value_type initial_voltage, fvm_value_type temperature_K);
+    void reset();
 };
 
 // For debugging only:
diff --git a/arbor/cable_cell.cpp b/arbor/cable_cell.cpp
index 7403dc1e7ab5f7e9aeb3dc066abf3bcc0d87b7be..bea58ad8b7e86b35ba31dfa9908e7e379e307063 100644
--- a/arbor/cable_cell.cpp
+++ b/arbor/cable_cell.cpp
@@ -85,6 +85,12 @@ cable_segment* cable_cell::cable(index_type index) {
     return cable? cable: throw cable_cell_error("segment is not a cable segment");
 }
 
+const cable_segment* cable_cell::cable(index_type index) const {
+    assert_valid_segment(index);
+    auto cable = segment(index)->as_cable();
+    return cable? cable: throw cable_cell_error("segment is not a cable segment");
+}
+
 std::vector<size_type> cable_cell::compartment_counts() const {
     std::vector<size_type> comp_count;
     comp_count.reserve(num_segments());
@@ -108,6 +114,55 @@ void cable_cell::add_detector(segment_location loc, double threshold) {
     spike_detectors_.push_back({loc, threshold});
 }
 
+// Approximating wildly by ignoring O(x) effects entirely, the attenuation b
+// over a single cable segment with constant resistivity R and membrane
+// capacitance C is given by:
+//
+// b = 2√(πRCf) · Σ 2L/(√d₀ + √d₁)
+//
+// where the sum is taken over each piecewise linear segment of length L
+// with diameters d₀ and d₁ at each end.
+
+value_type cable_cell::segment_mean_attenuation(
+    value_type frequency, index_type segidx,
+    const cable_cell_parameter_set& global_defaults) const
+{
+    value_type R = default_parameters.axial_resistivity.value_or(global_defaults.axial_resistivity.value());
+    value_type C = default_parameters.membrane_capacitance.value_or(global_defaults.membrane_capacitance.value());
+
+    value_type length_factor = 0; // [1/õm]
+
+    if (segidx==0) {
+        if (const soma_segment* s = soma()) {
+            R = s->parameters.axial_resistivity.value_or(R);
+            C = s->parameters.membrane_capacitance.value_or(C);
+
+            value_type d = 2*s->radius();
+            length_factor = 1/std::sqrt(d);
+        }
+    }
+    else {
+        const cable_segment* s = cable(segidx);
+        const auto& lengths = s->lengths();
+        const auto& radii = s->radii();
+
+        value_type total_length = 0;
+        R = s->parameters.axial_resistivity.value_or(R);
+        C = s->parameters.membrane_capacitance.value_or(C);
+
+        for (std::size_t i = 0; i<lengths.size(); ++i) {
+            length_factor += 2*lengths[i]/(std::sqrt(radii[i])+std::sqrt(radii[i+1]));
+            total_length += lengths[i];
+        }
+        length_factor /= total_length;
+    }
+
+    // R*C is in [s·cm/m²]; need to convert to [s/µm]
+    value_type tau_per_um = R*C*1e-8;
+
+    return 2*std::sqrt(math::pi<double>*tau_per_um*frequency)*length_factor; // [1/µm]
+}
+
 
 // Construct cell from flat morphology specification.
 
diff --git a/arbor/cable_cell_param.cpp b/arbor/cable_cell_param.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a60bcfb2a2facd822240752a7cb242d0629dd62b
--- /dev/null
+++ b/arbor/cable_cell_param.cpp
@@ -0,0 +1,67 @@
+#include <cfloat>
+#include <cmath>
+
+#include <arbor/cable_cell_param.hpp>
+
+#include "util/maputil.hpp"
+
+namespace arb {
+
+void check_global_properties(const cable_cell_global_properties& G) {
+    auto& param = G.default_parameters;
+
+    if (!param.init_membrane_potential) {
+        throw cable_cell_error("missing global default parameter value: init_membrane_potential");
+    }
+
+    if (!param.temperature_K) {
+        throw cable_cell_error("missing global default parameter value: temperature");
+    }
+
+    if (!param.axial_resistivity) {
+        throw cable_cell_error("missing global default parameter value: axial_resistivity");
+    }
+
+    if (!param.membrane_capacitance) {
+        throw cable_cell_error("missing global default parameter value: membrane_capacitance");
+    }
+
+    for (const auto& ion: util::keys(G.ion_species)) {
+        if (!param.ion_data.count(ion)) {
+            throw cable_cell_error("missing ion defaults for ion "+ion);
+        }
+    }
+
+    for (const auto& kv: param.ion_data) {
+        auto& ion = kv.first;
+        const cable_cell_ion_data& data = kv.second;
+        if (std::isnan(data.init_int_concentration)) {
+            throw cable_cell_error("missing init_int_concentration for ion "+ion);
+        }
+        if (std::isnan(data.init_ext_concentration)) {
+            throw cable_cell_error("missing init_ext_concentration for ion "+ion);
+        }
+        if (std::isnan(data.init_reversal_potential) && !param.reversal_potential_method.count(ion)) {
+            throw cable_cell_error("missing init_reversal_potential or reversal_potential_method for ion "+ion);
+        }
+    }
+}
+
+cable_cell_local_parameter_set neuron_parameter_defaults = {
+    // ion defaults: internal concentration [mM], external concentration [mM], reversal potential [mV]
+    {
+        {"na", {10.0,  140.0,  115 - 65.}},
+        {"k",  {54.4,    2.5,  -12 - 65.}},
+        {"ca", {5e-5,    2.0,  12.5*std::log(2.0/5e-5)}}
+    },
+    // initial membrane potential [mV]
+    -65.0,
+    // temperatue [K]
+    6.3 + 273.15,
+    // axial resistivity [Ω·cm]
+    35.4,
+    // membrane capacitance [F/m²]
+    0.01
+};
+
+} // namespace arb
diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp
index dd4d5b51042516ff61153b06ccbe808ffaf4a07d..f035b70b88f8b2290a281a8af1f5a3ce4d08d8c8 100644
--- a/arbor/fvm_layout.cpp
+++ b/arbor/fvm_layout.cpp
@@ -5,6 +5,7 @@
 
 #include <arbor/arbexcept.hpp>
 #include <arbor/cable_cell.hpp>
+#include <arbor/util/optional.hpp>
 
 #include "algorithms.hpp"
 #include "fvm_compartment.hpp"
@@ -19,7 +20,9 @@
 namespace arb {
 
 using util::count_along;
+using util::keys;
 using util::make_span;
+using util::optional;
 using util::subrange_view;
 using util::transform_view;
 using util::value_by_key;
@@ -34,6 +37,14 @@ namespace {
         }
     }
 
+    template <typename ResizableContainer, typename Index>
+    auto& extend_at(ResizableContainer& c, const Index& i) {
+        if (util::size(c)<=i) {
+            c.resize(i+1);
+        }
+        return c[i];
+    }
+
     struct compartment_model {
         arb::tree tree;
         std::vector<tree::int_type> parent_index;
@@ -59,6 +70,73 @@ namespace {
     };
 
     ARB_DEFINE_LEXICOGRAPHIC_ORDERING(cv_param,(a.cv,a.params,a.target),(b.cv,b.params,b.target))
+
+    template <typename V>
+    optional<V> operator|(const optional<V>& a, const optional<V>& b) {
+        return a? a: b;
+    }
+
+    // For each segment given by the provided sorted sequence of segment
+    // indices, call `action` for each CV intersecting the segment, starting
+    // from the most proximal.
+    //
+    // By the ordering of CVs and segments in the discretization, with the
+    // exception of the most proximal CV in a segment, each CV will be visited
+    // once, and the visited CVs will be in increasing order. The most proximal
+    // CV (the 'parent' CV) may be visited multiple times.
+    //
+    // Action is a functional that takes the following arguments:
+    //
+    //     size_type  cv_index   The index into the total (sorted) list of
+    //                           CVs that constitute the segments.
+    //
+    //     index_type cv         The CV number (within the discretization).
+    //
+    //     value_type cv_area    The area of the CV that lies within the
+    //                           current segment.
+    //
+    //     index_type seg_index  The index into the provided sequence of
+    //                           the provided segment_indices.
+    //
+    //     index_type seg        The segment currently being iterated over.
+
+    template <typename Seq, typename Action>
+    void for_each_cv_in_segments(const fvm_discretization& D, const Seq& segment_indices, const Action& action) {
+        using index_type = fvm_index_type;
+        using size_type = fvm_size_type;
+
+        std::unordered_map<index_type, size_type> parent_cv_indices;
+        size_type cv_index = 0;
+
+        index_type seg_index = 0;
+        for (const auto& seg: segment_indices) {
+            const segment_info& seg_info = D.segments[seg];
+
+            if (seg_info.has_parent()) {
+                index_type cv = seg_info.parent_cv;
+
+                size_type i = parent_cv_indices.insert({cv, cv_index}).first->second;
+                if (i==cv_index) {
+                    ++cv_index;
+                }
+
+                action(i, cv, seg_info.parent_cv_area, seg_index, seg);
+            }
+
+            for (index_type cv = seg_info.proximal_cv; cv < seg_info.distal_cv; ++cv) {
+                index_type i = cv_index++;
+                action(i, cv, D.cv_area[cv], seg_index, seg);
+            }
+
+            index_type cv = seg_info.distal_cv;
+            size_type i = cv_index++;
+
+            action(i, seg_info.distal_cv, seg_info.distal_cv_area, seg_index, seg);
+            parent_cv_indices.insert({cv, i});
+            ++seg_index;
+        }
+    }
+
 } // namespace
 
 // Cable segment discretization
@@ -123,7 +201,7 @@ namespace {
 //       = 1/R · hV₁V₂/(h₂²V₁+h₁²V₂)
 //
 
-fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells) {
+fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells, const cable_cell_parameter_set& global_defaults) {
 
     using value_type = fvm_value_type;
     using index_type = fvm_index_type;
@@ -153,6 +231,8 @@ fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells) {
     D.face_conductance.assign(D.ncv, 0.);
     D.cv_area.assign(D.ncv, 0.);
     D.cv_capacitance.assign(D.ncv, 0.);
+    D.init_membrane_potential.assign(D.ncv, 0.);
+    D.temperature_K.assign(D.ncv, 0.);
     D.parent_cv.assign(D.ncv, index_type(-1));
     D.cv_to_cell.resize(D.ncv);
     for (auto i: make_span(0, D.ncell)) {
@@ -170,6 +250,12 @@ fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells) {
             D.parent_cv[k] = cell_graph.parent_index[k-cell_cv_base]+cell_cv_base;
         }
 
+        // Electrical defaults from global defaults, possibly overridden by cell.
+        auto cm_default = c.default_parameters.membrane_capacitance | global_defaults.membrane_capacitance;
+        auto rL_default = c.default_parameters.axial_resistivity | global_defaults.axial_resistivity;
+        auto init_vm_default = c.default_parameters.init_membrane_potential | global_defaults.init_membrane_potential;
+        auto temp_default = c.default_parameters.temperature_K | global_defaults.temperature_K;
+
         // Compartment index range for each segment in this cell.
         seg_cv_bounds.clear();
         auto seg_cv_part = make_partition(
@@ -206,6 +292,9 @@ fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells) {
         soma_info.distal_cv_area = soma_area;
         D.segments.push_back(soma_info);
 
+        index_type soma_segment_index = D.segments.size()-1;
+        D.parent_segment.push_back(soma_segment_index);
+
         // Other segments must all be cable segments.
         for (size_type j = 1; j<nseg; ++j) {
             const auto& seg_cv_ival = seg_cv_part[j];
@@ -217,8 +306,12 @@ fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells) {
             if (!cable) {
                 throw arbor_internal_error("fvm_layout: non-root segments of cell must be cable segments");
             }
-            auto cm = cable->cm;    // [F/m²]
-            auto rL = cable->rL;    // [Ω·cm]
+
+            const auto& params = cable->parameters;
+            auto cm = (params.membrane_capacitance | cm_default).value(); // [F/m²]
+            auto rL = (params.axial_resistivity | rL_default).value(); // [Ω·cm]
+            auto init_vm = (params.init_membrane_potential | init_vm_default).value(); // [mV]
+            auto temp = (params.temperature_K | temp_default).value(); // [mV]
 
             bool soma_parent = c.parent(j)->as_soma() ? true : false; //segment's parent is a soma
 
@@ -226,7 +319,7 @@ fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells) {
             auto lengths = cable->lengths();
 
             // If segment has soma parent, send soma information to div_compartment_integrator
-            if(soma_parent) {
+            if (soma_parent) {
                 radii.insert(radii.begin(), soma->radius());
                 lengths.insert(lengths.begin(), soma->radius()*2);
             }
@@ -242,6 +335,19 @@ fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells) {
             seg_info.distal_cv_area = divs(ncv-1).right.area;
 
             D.segments.push_back(seg_info);
+            if (soma_parent) {
+                D.parent_segment.push_back(soma_segment_index);
+            }
+            else {
+                auto opt_index = util::binary_search_index(D.segments, seg_info.parent_cv,
+                    [&D](const segment_info& seg_info) { return seg_info.distal_cv; });
+
+                if (!opt_index) {
+                    throw arbor_internal_error("fvm_layout: could not find parent segment");
+                }
+                D.parent_segment.push_back(*opt_index);
+            }
+
 
             for (auto i: make_span(seg_cv_ival)) {
                 const auto& div = divs(i-seg_cv_ival.first);
@@ -261,14 +367,33 @@ fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells) {
                 auto ar = div.right.area;        // [µm²]
 
                 D.cv_area[j] += al;              // [µm²]
-                D.cv_capacitance[j] += al * cm;  // [pF]
+                D.cv_capacitance[j] += al*cm;    // [pF]
+                D.init_membrane_potential[j] += al*init_vm;  // [mV·µm²]
+                D.temperature_K[j] += al*temp;   // [K·µm²]
+
                 D.cv_area[i] += ar;              // [µm²]
-                D.cv_capacitance[i] += ar * cm;  // [pF]
+                D.cv_capacitance[i] += ar*cm;    // [pF]
+                D.init_membrane_potential[i] += ar*init_vm;  // [mV·µm²]
+                D.temperature_K[i] += ar*temp;   // [K·µm²]
             }
         }
 
+        auto soma_cm = (soma->parameters.membrane_capacitance | cm_default).value(); // [F/m²]
+        auto soma_init_vm = (soma->parameters.init_membrane_potential | init_vm_default).value(); // [mV]
+        auto soma_temp = (soma->parameters.temperature_K | temp_default).value();    // [mV]
+
         D.cv_area[soma_cv] = soma_area;                  // [µm²]
-        D.cv_capacitance[soma_cv] = soma_area*soma->cm;  // [pF]
+        D.cv_capacitance[soma_cv] = soma_area*soma_cm;   // [pF]
+        D.init_membrane_potential[soma_cv] = soma_area*soma_init_vm; // [mV·µm²]
+        D.temperature_K[soma_cv] = soma_area*soma_temp;  // [K·µm²]
+    }
+
+    // Rescale CV init_vm and temperature values to get area-weighted means.
+    for (auto i: make_span(0, D.ncv)) {
+        if (D.cv_area[i]) {
+            D.init_membrane_potential[i] /= D.cv_area[i]; // [mV]
+            D.temperature_K[i] /= D.cv_area[i]; // [mV]
+        }
     }
 
     // Number of CVs per cell is exactly number of compartments.
@@ -301,6 +426,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
     using string_index_map = std::unordered_map<std::string, size_type>;
 
     const mechanism_catalogue& catalogue = *gprop.catalogue;
+    const cable_cell_parameter_set& gparam = gprop.default_parameters;
 
     fvm_mechanism_data mechdata;
 
@@ -312,12 +438,24 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
     //     3. Pointer to mechanism metadata from catalogue.
 
     struct density_mech_data {
-        std::vector<std::pair<size_type, const mechanism_desc*>> segments; //
+        std::vector<std::pair<size_type, const mechanism_desc*>> segments;
         string_set paramset;
         std::shared_ptr<mechanism_info> info = nullptr;
     };
     std::unordered_map<std::string, density_mech_data> density_mech_table;
 
+    // Temporary table for revpot mechanism info, mapping mechanism name to tuple of:
+    //     1. Sorted map from cell index to mechanism parameter settings.
+    //     2. Set of the names of parameters that are anywhere modified.
+    //     3. Pointer to mechanism metadat from catalogue.
+
+    struct revpot_mech_data {
+        std::map<size_type, const mechanism_desc*> cells;
+        string_set paramset;
+        std::shared_ptr<mechanism_info> info = nullptr;
+    };
+    std::unordered_map<std::string, revpot_mech_data> revpot_mech_table;
+
     // Temporary table for point mechanism info, mapping mechanism name to tuple:
     //     1. Vector of point info: CV, index into cell group targets, parameter settings.
     //     2. Set of the names of parameters that are anywhere modified.
@@ -337,38 +475,34 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
 
     // Built-in stimulus mechanism data is dealt with especially below.
     // Record for each stimulus the CV and clamp data.
+
     std::vector<std::pair<size_type, i_clamp>> stimuli;
 
-    // Temporary table for presence of ion channels, mapping ion name to _sorted_
-    // collection of segment indices.
+    // Temporary table for presence of ion channels, mapping ion name to a _sorted_
+    // collection of per-segment ion data, viz. a map from segment index to
+    // initial internal and external concentrations and reversal potentials, plus
+    // information about whether there is a mechanism that requires this ion
+    // on this segment, and if that ion writes to internal or external concentration.
+
+    struct ion_segment_data {
+        cable_cell_ion_data ion_data;
+        bool mech_requires = false;
+        bool mech_writes_iconc = false;
+        bool mech_writes_econc = false;
+    };
 
-    std::unordered_map<std::string, std::set<size_type>> ion_segments;
+    std::unordered_map<std::string, std::map<index_type, ion_segment_data>> ion_segments;
 
-    auto update_paramset_and_validate =
-        [&catalogue]
-        (const mechanism_desc& desc, std::shared_ptr<mechanism_info>& info, string_set& paramset)
-    {
-        auto& name = desc.name();
-        if (!info) {
-            info.reset(new mechanism_info(catalogue[name]));
-        }
-        for (const auto& pv: desc.values()) {
-            if (!paramset.count(pv.first)) {
-                if (!info->parameters.count(pv.first)) {
-                    throw no_such_parameter(name, pv.first);
-                }
-                if (!info->parameters.at(pv.first).valid(pv.second)) {
-                    throw invalid_parameter_value(name, pv.first, pv.second);
-                }
-                paramset.insert(pv.first);
-            }
-        }
-    };
+    // Temporary table for presence of mechanisms that read the reversal potential
+    // of an ion channel, mapping ion name and cell index to a _sorted_
+    // collection of segment indices.
+
+    std::unordered_map<std::string, std::unordered_map<size_type, std::set<size_type>>> ion_revpot_segments;
 
     auto verify_ion_usage =
         [&gprop](const std::string& mech_name, const mechanism_info* info)
     {
-        const auto& global_ions = gprop.ion_default;
+        const auto& global_ions = gprop.ion_species;
 
         for (const auto& ion: info->ions) {
             const auto& ion_name = ion.first;
@@ -380,7 +514,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
             }
 
             if (ion_dep.verify_ion_charge) {
-                if (ion_dep.expected_ion_charge!=global_ions.at(ion_name).charge) {
+                if (ion_dep.expected_ion_charge!=global_ions.at(ion_name)) {
                     throw cable_cell_error(
                         "mechanism "+mech_name+" uses ion "+ion_name+ " expecting a different valence");
                 }
@@ -388,6 +522,28 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
         }
     };
 
+    auto update_paramset_and_validate =
+        [&catalogue,&verify_ion_usage]
+        (const mechanism_desc& desc, std::shared_ptr<mechanism_info>& info, string_set& paramset)
+    {
+        auto& name = desc.name();
+        if (!info) {
+            info.reset(new mechanism_info(catalogue[name]));
+            verify_ion_usage(name, info.get());
+        }
+        for (const auto& pv: desc.values()) {
+            if (!paramset.count(pv.first)) {
+                if (!info->parameters.count(pv.first)) {
+                    throw no_such_parameter(name, pv.first);
+                }
+                if (!info->parameters.at(pv.first).valid(pv.second)) {
+                    throw invalid_parameter_value(name, pv.first, pv.second);
+                }
+                paramset.insert(pv.first);
+            }
+        }
+    };
+
     auto cell_segment_part = D.cell_segment_part();
     size_type target_id = 0;
 
@@ -395,16 +551,61 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
         auto& cell = cells[cell_idx];
         auto seg_range = cell_segment_part[cell_idx];
 
+        auto add_ion_segment =
+            [&gparam, &cell, &ion_segments]
+            (const std::string& ion_name, size_type segment_idx, const cable_cell_local_parameter_set& seg_param, const ion_dependency* iondep = nullptr)
+        {
+            const auto& global_ion_data = gparam.ion_data;
+            const auto& cell_ion_data = cell.default_parameters.ion_data;
+            const auto& seg_ion_data = seg_param.ion_data;
+
+            auto& ion_entry = ion_segments[ion_name];
+
+            // New entry?
+            util::optional<cable_cell_ion_data> opt_ion_data;
+            if (!ion_entry.count(segment_idx)) {
+                opt_ion_data = value_by_key(seg_ion_data, ion_name);
+                if (!opt_ion_data) {
+                    opt_ion_data = value_by_key(cell_ion_data, ion_name);
+                }
+                if (!opt_ion_data) {
+                    opt_ion_data = value_by_key(global_ion_data, ion_name);
+                }
+                if (!opt_ion_data) {
+                    throw arbor_internal_error("missing entry for ion "+ion_name+" in cable_cell global defaults");
+                }
+            }
+
+            auto& ion_entry_seg = ion_entry[segment_idx];
+            if (opt_ion_data) {
+                ion_entry_seg.ion_data = *opt_ion_data;
+            }
+            if (iondep) {
+                ion_entry_seg.mech_requires = true;
+                ion_entry_seg.mech_writes_iconc |= iondep->write_concentration_int;
+                ion_entry_seg.mech_writes_econc |= iondep->write_concentration_ext;
+            }
+        };
+
         for (auto segment_idx: make_span(seg_range)) {
-            for (const mechanism_desc& desc: cell.segments()[segment_idx-seg_range.first]->mechanisms()) {
+            const segment_ptr& seg = cell.segments()[segment_idx-seg_range.first];
+
+            for (const mechanism_desc& desc: seg->mechanisms()) {
                 const auto& name = desc.name();
 
                 density_mech_data& entry = density_mech_table[name];
                 update_paramset_and_validate(desc, entry.info, entry.paramset);
                 entry.segments.emplace_back(segment_idx, &desc);
 
-                for (const auto& ion: entry.info->ions) {
-                    ion_segments[ion.first].insert(segment_idx);
+                for (const auto& ion_entry: entry.info->ions) {
+                    const std::string& ion_name = ion_entry.first;
+                    const ion_dependency& iondep = ion_entry.second;
+
+                    add_ion_segment(ion_name, segment_idx, seg->parameters, &iondep);
+
+                    if (ion_entry.second.read_reversal_potential) {
+                        ion_revpot_segments[ion_name][cell_idx].insert(segment_idx);
+                    }
                 }
             }
         }
@@ -418,9 +619,18 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
             update_paramset_and_validate(desc, entry.info, entry.paramset);
             entry.points.push_back({cv, target_id++, &desc});
 
+            const segment_ptr& seg = cell.segments()[cellsyn.location.segment];
             size_type segment_idx = D.cell_segment_bounds[cell_idx]+cellsyn.location.segment;
-            for (const auto& ion: entry.info->ions) {
-                ion_segments[ion.first].insert(segment_idx);
+
+            for (const auto& ion_entry: entry.info->ions) {
+                const std::string& ion_name = ion_entry.first;
+                const ion_dependency& iondep = ion_entry.second;
+
+                add_ion_segment(ion_name, segment_idx, seg->parameters, &iondep);
+
+                if (ion_entry.second.read_reversal_potential) {
+                    ion_revpot_segments[ion_name][cell_idx].insert(segment_idx);
+                }
             }
         }
 
@@ -428,22 +638,85 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
             size_type cv = D.segment_location_cv(cell_idx, stimulus.location);
             stimuli.push_back({cv, stimulus.clamp});
         }
-    }
 
-    for (auto& entry: density_mech_table) {
-        verify_ion_usage(entry.first, entry.second.info.get());
-    }
+        // Add segments to ion_segments map which intersect with existing segments, so
+        // that each CV with an ion value is 100% covered.
+
+        for (auto& e: ion_segments) {
+            const auto& name = e.first;
+            const auto& ion_entry = e.second;
+
+            for (auto segment_idx: make_span(seg_range)) {
+                index_type parent_segment_idx = D.parent_segment[segment_idx];
+                const segment_ptr& parent_seg = cell.segments()[parent_segment_idx-seg_range.first];
+
+                if (ion_entry.count(segment_idx)) {
+                    add_ion_segment(name, parent_segment_idx, parent_seg->parameters);
+                }
+            }
+
+            for (auto segment_idx: make_span(seg_range)) {
+                const segment_ptr& seg = cell.segments()[segment_idx-seg_range.first];
+                index_type parent_segment_idx = D.parent_segment[segment_idx];
+
+                if (ion_entry.count(parent_segment_idx)) {
+                    add_ion_segment(name, segment_idx, seg->parameters);
+                }
+            }
+        }
 
-    for (auto& entry: point_mech_table) {
-        verify_ion_usage(entry.first, entry.second.info.get());
+
+        // Maintain a map of reversal potential mechanism to written ions for this
+        // cell, to ensure consistency with assignments from the cell and global
+        // parameters.
+
+        std::unordered_multimap<std::string, std::string> revpot_to_ion;
+        std::unordered_map<std::string, std::string> ion_to_revpot;
+
+        auto add_revpot = [&](const std::string& ion, const mechanism_desc& desc) {
+            const auto& name = desc.name();
+
+            revpot_mech_data& entry = revpot_mech_table[name];
+            update_paramset_and_validate(desc, entry.info, entry.paramset);
+            ion_to_revpot[ion] = desc.name();
+            for (auto dep: entry.info->ions) {
+                if (dep.second.write_reversal_potential) {
+                    revpot_to_ion.insert({desc.name(), dep.first});
+                }
+            }
+            entry.cells.insert({cell_idx, &desc});
+        };
+
+        const auto& cellrevpot = cell.default_parameters.reversal_potential_method;
+        for (const auto& revpot: cellrevpot) {
+            add_revpot(revpot.first, revpot.second);
+        }
+
+        const auto& globalrevpot =  gparam.reversal_potential_method;
+        for (const auto& revpot: globalrevpot) {
+            if (!cellrevpot.count(revpot.first)) {
+                add_revpot(revpot.first, revpot.second);
+            }
+        }
+
+        // Ensure that if a revpot mechanism writes to multiple ions, that
+        // that mechanism is associated with all of them.
+        for (auto& entry: revpot_to_ion) {
+            auto declared_revpot = value_by_key(ion_to_revpot, entry.second);
+            if (!declared_revpot || declared_revpot.value()!=entry.first) {
+                throw cable_cell_error("mechanism "+entry.first+" writes reversal potential for "
+                    +entry.second+", which is not configured to use "+entry.first);
+            }
+        }
     }
 
+
     // II. Build ion and mechanism configs.
 
     // Shared temporary lookup info across mechanism instances, set by build_param_data.
-    string_index_map param_index;
-    std::vector<std::string> param_name;
-    std::vector<value_type> param_default;
+    string_index_map param_index;           // maps parameter name to parameter index
+    std::vector<std::string> param_name;    // indexed by parameter index
+    std::vector<value_type> param_default;  // indexed by parameter index
 
     auto build_param_data =
         [&param_name, &param_index, &param_default](const string_set& paramset, const mechanism_info* info)
@@ -463,33 +736,109 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
 
     // IIa. Ion channel CVs.
 
-    for (auto& ionseg: ion_segments) {
-        auto& ion = mechdata.ions[ionseg.first];
+    for (const auto& ionseg: ion_segments) {
+        auto& seg_ion_map = ion_segments[ionseg.first];
+        fvm_ion_config& ion_config = mechdata.ions[ionseg.first];
+        auto& seg_ion_data = ionseg.second;
+
+        for_each_cv_in_segments(D, keys(seg_ion_data),
+            [&ion_config, &seg_ion_map](size_type cv_index, index_type cv, value_type area, index_type seg_index, index_type seg) {
+                if (seg_ion_map[seg].mech_requires) {
+                    if (!util::binary_search_index(ion_config.cv, cv)) {
+                        ion_config.cv.push_back(cv);
+                    }
+                }
+            });
 
-        for (size_type segment: ionseg.second) {
-            const segment_info& seg_info = D.segments[segment];
+        ion_config.init_iconc.resize(ion_config.cv.size());
+        ion_config.init_econc.resize(ion_config.cv.size());
+        ion_config.init_revpot.resize(ion_config.cv.size());
 
-            if (seg_info.has_parent()) {
-                index_type cv = seg_info.parent_cv;
-                optional<std::size_t> parent_idx = util::binary_search_index(ion.cv, cv);
-                if (!parent_idx) {
-                    ion.cv.push_back(cv);
-                    ion.iconc_norm_area.push_back(D.cv_area[cv]);
-                    ion.econc_norm_area.push_back(D.cv_area[cv]);
+        for_each_cv_in_segments(D, keys(seg_ion_data),
+            [&ion_config, &seg_ion_map, &D](size_type cv_index, index_type cv, value_type area, index_type seg_index, index_type seg) {
+                auto opt_i = util::binary_search_index(ion_config.cv, cv);
+                if (!opt_i) return;
+
+                std::size_t i = *opt_i;
+                auto& seg_ion_entry = seg_ion_map[seg];
+
+                value_type weight = area/D.cv_area[cv];
+                if (!seg_ion_entry.mech_writes_iconc) {
+                    ion_config.init_iconc[i] += weight*seg_ion_entry.ion_data.init_int_concentration;
+                }
+
+                if (!seg_ion_entry.mech_writes_econc) {
+                    ion_config.init_econc[i] += weight*seg_ion_entry.ion_data.init_ext_concentration;
+                }
+
+                // Reversal potentials are not area weighted, and are overridden at the
+                // per-cell level by any supplied revpot mechanisms.
+                ion_config.init_revpot[i] = seg_ion_entry.ion_data.init_reversal_potential;
+            });
+    }
+
+    // IIb. Reversal potential mechanism CVs and parameters.
+
+    for (const auto& entry: revpot_mech_table) {
+        const std::string& name = entry.first;
+        const mechanism_info& info = *entry.second.info;
+
+        fvm_mechanism_config& config = mechdata.mechanisms[name];
+        config.kind = mechanismKind::revpot;
+
+        auto nparam = build_param_data(entry.second.paramset, &info);
+        config.param_values.resize(nparam);
+        for (auto pidx: make_span(nparam)) {
+            config.param_values[pidx].first = param_name[pidx];
+        }
+
+        for (auto& cell_entry: entry.second.cells) {
+            auto cell_idx = cell_entry.first;
+            std::vector<index_type> segment_indices;
+
+            for (auto& mech_ion_dep_entry: info.ions) {
+                auto& ion_name = mech_ion_dep_entry.first;
+                auto& ion_dep = mech_ion_dep_entry.second;
+
+                if (!ion_dep.write_reversal_potential) continue;
+
+                const auto& segments = value_by_key(ion_revpot_segments[ion_name], cell_idx);
+                if (!segments) continue;
+
+                for (auto seg_idx: segments.value()) {
+                    segment_indices.push_back(seg_idx);
                 }
             }
 
-            for (auto cv: make_span(seg_info.cv_range())) {
-                ion.cv.push_back(cv);
-                ion.iconc_norm_area.push_back(D.cv_area[cv]);
-                ion.econc_norm_area.push_back(D.cv_area[cv]);
+            const mechanism_desc& desc = *cell_entry.second;
+            std::vector<value_type> pval = param_default;
+
+            for (auto pidx: make_span(nparam)) {
+                if (auto opt_v = value_by_key(desc.values(), param_name[pidx])) {
+                    pval[pidx] = opt_v.value();
+                }
             }
+
+            size_type config_offset = config.cv.size();
+            for_each_cv_in_segments(D, segment_indices,
+                [&](size_type cv_index, index_type cv, auto, auto, auto) {
+                    extend_at(config.cv, config_offset+cv_index) = cv;
+
+                    for (auto pidx: make_span(nparam)) {
+                        extend_at(config.param_values[pidx].second, config_offset+cv_index) = pval[pidx];
+                    }
+                });
         }
     }
 
-    // IIb. Density mechanism CVs, parameters and ionic default concentration contributions.
+    // Remove any reversal potential mechanisms that ultimately have no extent.
+    for (auto i = mechdata.mechanisms.begin(); i!=mechdata.mechanisms.end(); ) {
+        i = i->second.cv.empty()? mechdata.mechanisms.erase(i): std::next(i);
+    }
+
+    // IIc. Density mechanism CVs, parameters and ionic default concentration contributions.
 
-    // Ameliorate area sum rounding areas by clamping normalized area contributions to [0, 1]
+    // Ameliorate area sum rounding errors by clamping normalized area contributions to [0, 1]
     // and rounding values within an epsilon of 0 or 1 to that value.
     auto trim = [](value_type& v) {
         constexpr value_type eps = std::numeric_limits<value_type>::epsilon()*4;
@@ -503,7 +852,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
 
         auto nparam = build_param_data(entry.second.paramset, entry.second.info.get());
 
-        // In order to properly account for partially overriden paramaters in CVs
+        // In order to properly account for partially overriden parameters in CVs
         // that are shared between segments, we need to track not only the area-weighted
         // sum of parameter values, but also the total area for each CV for each parameter
         // that has been overriden — the remaining area demands a contribution from the
@@ -512,65 +861,23 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
         std::vector<std::vector<value_type>> param_value(nparam);
         std::vector<std::vector<value_type>> param_area_contrib(nparam);
 
-        // (gcc 6.x bug fails to deduce const in lambda capture reference initialization)
-        const auto& info = *entry.second.info;
-        auto accumulate_mech_data =
-            [
-                &info,
-                &ion_configs = mechdata.ions,
-                &param_index, &param_value, &param_area_contrib, &config
-            ]
-            (size_type index, index_type cv, value_type area, const mechanism_desc& desc)
-        {
-            for (auto& kv: desc.values()) {
-                int pidx = param_index.at(kv.first);
-                value_type v = kv.second;
+        for_each_cv_in_segments(D, keys(entry.second.segments),
+            [&](size_type cv_index, index_type cv, value_type area, index_type seg_index, index_type seg)
+            {
+                const mechanism_desc& mech_desc = *entry.second.segments[seg_index].second;
 
-                extend_to(param_area_contrib[pidx], index);
-                param_area_contrib[pidx][index] += area;
+                extend_at(config.cv, cv_index) = cv;
 
-                extend_to(param_value[pidx], index);
-                param_value[pidx][index] += area*v;
-            }
+                for (auto& kv: mech_desc.values()) {
+                    int pidx = param_index.at(kv.first);
+                    value_type v = kv.second;
 
-            for (auto& ion: info.ions) {
-                fvm_ion_config& ion_config = ion_configs[ion.first];
-                size_type index = util::binary_search_index(ion_config.cv, cv).value();
-                if (ion.second.write_concentration_int) {
-                    ion_config.iconc_norm_area[index] -= area;
-                }
-                if (ion.second.write_concentration_ext) {
-                    ion_config.econc_norm_area[index] -= area;
+                    extend_at(param_area_contrib[pidx], cv_index) += area;
+                    extend_at(param_value[pidx], cv_index) += area*v;
                 }
-            }
 
-            extend_to(config.norm_area, index);
-            config.norm_area[index] += area;
-        };
-
-        for (auto& seg_entry: entry.second.segments) {
-            const segment_info& seg_info = D.segments[seg_entry.first];
-            const mechanism_desc& mech_desc = *seg_entry.second;
-
-            if (seg_info.has_parent()) {
-                index_type cv = seg_info.parent_cv;
-                optional<std::size_t> parent_idx = util::binary_search_index(config.cv, cv);
-                if (!parent_idx) {
-                    parent_idx = config.cv.size();
-                    config.cv.push_back(cv);
-                }
-
-                accumulate_mech_data(*parent_idx, cv, seg_info.parent_cv_area, mech_desc);
-            }
-
-            for (auto cv: make_span(seg_info.cv_range())) {
-                size_type idx = config.cv.size();
-                config.cv.push_back(cv);
-
-                value_type area = cv==seg_info.distal_cv? seg_info.distal_cv_area: D.cv_area[cv];
-                accumulate_mech_data(idx, cv, area, mech_desc);
-            }
-        }
+                extend_at(config.norm_area, cv_index) += area;
+            });
 
         // Complete parameter values with default values.
 
@@ -599,20 +906,6 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
         }
     }
 
-    // Normalize ion norm_area entries.
-
-    for (auto& entry: mechdata.ions) {
-        auto& ion_config = entry.second;
-        for (auto i: count_along(ion_config.cv)) {
-            auto cv_area = D.cv_area[ion_config.cv[i]];
-            ion_config.iconc_norm_area[i] /= cv_area;
-            trim(ion_config.iconc_norm_area[i]);
-
-            ion_config.econc_norm_area[i] /= cv_area;
-            trim(ion_config.econc_norm_area[i]);
-        }
-    }
-
     // II.3 Point mechanism CVs, targets, parameters and stimuli.
 
     for (const auto& entry: point_mech_table) {
@@ -638,7 +931,7 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties&
         // Generate config.param_values: contains parameters of group of synapses that can be coalesced into one instance
         // Generate multiplicity: contains number of synapses in each coalesced group of synapses
         // Generate target: contains the synapse target number
-        if(catalogue[name].linear && gprop.coalesce_synapses) {
+        if (catalogue[name].linear && gprop.coalesce_synapses) {
             // cv_param_vec used to lexicographically sort the cv, parameters and target, which are stored in that order
             std::vector<cv_param> cv_param_vec(cv_order.size());
 
diff --git a/arbor/fvm_layout.hpp b/arbor/fvm_layout.hpp
index a87814ef9396dcdc901edc9f6503cde05f895495..c0172e06e21ba10a15cba1c117a6bcc1d6298b70 100644
--- a/arbor/fvm_layout.hpp
+++ b/arbor/fvm_layout.hpp
@@ -66,8 +66,14 @@ struct fvm_discretization {
     std::vector<value_type> face_conductance; // [µS]
     std::vector<value_type> cv_area;          // [µm²]
     std::vector<value_type> cv_capacitance;   // [pF]
+    std::vector<value_type> init_membrane_potential; // [mV]
+    std::vector<value_type> temperature_K;    // [K]
 
     std::vector<segment_info> segments;
+
+    // If segment has no parent segment, parent_segment[j] = j.
+    std::vector<index_type> parent_segment;
+
     std::vector<size_type> cell_segment_bounds; // Partitions segment indices by cell.
     std::vector<index_type> cell_cv_bounds;      // Partitions CV indices by cell.
 
@@ -88,7 +94,7 @@ struct fvm_discretization {
     }
 };
 
-fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells);
+fvm_discretization fvm_discretize(const std::vector<cable_cell>& cells, const cable_cell_parameter_set& params);
 
 
 // Post-discretization data for point and density mechanism instantiation.
@@ -103,7 +109,7 @@ struct fvm_mechanism_config {
     // duplicates for point mechanisms.
     std::vector<index_type> cv;
 
-    // Coalesced synapse multiplier
+    // Coalesced synapse multiplier (point mechanisms only).
     std::vector<index_type> multiplicity;
 
     // Normalized area contribution in corresponding CV (density mechanisms only).
@@ -126,8 +132,11 @@ struct fvm_ion_config {
     std::vector<index_type> cv;
 
     // Normalized area contribution of default concentration contribution in corresponding CV.
-    std::vector<value_type> iconc_norm_area;
-    std::vector<value_type> econc_norm_area;
+    std::vector<value_type> init_iconc;
+    std::vector<value_type> init_econc;
+
+    // Ion-specific (initial) reversal potential per CV.
+    std::vector<value_type> init_revpot;
 };
 
 struct fvm_mechanism_data {
diff --git a/arbor/fvm_lowered_cell_impl.hpp b/arbor/fvm_lowered_cell_impl.hpp
index b1b13406ca835ca02b6fe67a9964927d102979e3..a466077b19611dcc2132e511a8c49c1ab7f30290 100644
--- a/arbor/fvm_lowered_cell_impl.hpp
+++ b/arbor/fvm_lowered_cell_impl.hpp
@@ -18,6 +18,7 @@
 
 #include <arbor/assert.hpp>
 #include <arbor/common_types.hpp>
+#include <arbor/cable_cell_param.hpp>
 #include <arbor/recipe.hpp>
 
 #include "builtin_mechanisms.hpp"
@@ -100,9 +101,8 @@ private:
     threshold_watcher threshold_watcher_;
 
     value_type tmin_ = 0;
-    value_type initial_voltage_ = NAN;
-    value_type temperature_ = NAN;
-    std::vector<mechanism_ptr> mechanisms_;
+    std::vector<mechanism_ptr> mechanisms_; // excludes reversal potential calculators.
+    std::vector<mechanism_ptr> revpot_mechanisms_;
 
     // Non-physical voltage check threshold, 0 => no check.
     value_type check_voltage_mV = 0;
@@ -151,9 +151,13 @@ void fvm_lowered_cell_impl<Backend>::assert_tmin() {
 
 template <typename Backend>
 void fvm_lowered_cell_impl<Backend>::reset() {
-    state_->reset(initial_voltage_, temperature_);
+    state_->reset();
     set_tmin(0);
 
+    for (auto& m: revpot_mechanisms_) {
+        m->initialize();
+    }
+
     for (auto& m: mechanisms_) {
         m->initialize();
     }
@@ -198,6 +202,15 @@ fvm_integration_result fvm_lowered_cell_impl<Backend>::integrate(
     // complete fvm state into shared state object.
 
     while (remaining_steps) {
+        // Update any required reversal potentials based on ionic concs.
+
+        PE(advance_update_revpot)
+        for (auto& m: revpot_mechanisms_) {
+            m->nrn_current();
+        }
+        PL();
+
+
         // Deliver events and accumulate mechanism current contributions.
 
         PE(advance_integrate_events);
@@ -299,7 +312,6 @@ void fvm_lowered_cell_impl<B>::update_ion_state() {
     for (auto& m: mechanisms_) {
         m->write_ions();
     }
-    state_->ions_nernst_reversal_potential(temperature_);
 }
 
 template <typename B>
@@ -355,9 +367,11 @@ void fvm_lowered_cell_impl<B>::initialize(
         throw bad_global_property(cell_kind::cable);
     }
 
+    // Assert that all global default parameters have been set.
+    // (Throws cable_cell_error on failure.)
+    check_global_properties(global_props);
+
     const mechanism_catalogue* catalogue = global_props.catalogue;
-    initial_voltage_ = global_props.init_membrane_potential_mV;
-    temperature_ = global_props.temperature_K;
 
     // Mechanism instantiator helper.
     auto mech_instance = [&catalogue](const std::string& name) {
@@ -373,7 +387,7 @@ void fvm_lowered_cell_impl<B>::initialize(
 
     // Discretize cells, build matrix.
 
-    fvm_discretization D = fvm_discretize(cells);
+    fvm_discretization D = fvm_discretize(cells, global_props.default_parameters);
 
     std::vector<index_type> cv_to_intdom(D.ncv);
     std::transform(D.cv_to_cell.begin(), D.cv_to_cell.end(), cv_to_intdom.begin(),
@@ -387,7 +401,7 @@ void fvm_lowered_cell_impl<B>::initialize(
 
     fvm_mechanism_data mech_data = fvm_build_mechanism_data(global_props,  cells, D);
 
-    // Discritize and build gap junction info
+    // Discretize and build gap junction info.
 
     auto gj_vector = fvm_gap_junctions(cells, gids, rec, D);
 
@@ -398,15 +412,17 @@ void fvm_lowered_cell_impl<B>::initialize(
         util::transform_view(keys(mech_data.mechanisms),
             [&](const std::string& name) { return mech_instance(name).mech->data_alignment(); }));
 
-    state_ = std::make_unique<shared_state>(num_intdoms, cv_to_intdom, gj_vector, data_alignment? data_alignment: 1u);
+    state_ = std::make_unique<shared_state>(
+                num_intdoms, cv_to_intdom, gj_vector, D.init_membrane_potential, D.temperature_K,
+                data_alignment? data_alignment: 1u);
 
     // Instantiate mechanisms and ions.
 
     for (auto& i: mech_data.ions) {
         const std::string& ion_name = i.first;
 
-        if (auto ion = value_by_key(global_props.ion_default, ion_name)) {
-            state_->add_ion(ion_name, ion.value(), i.second.cv, i.second.iconc_norm_area, i.second.econc_norm_area);
+        if (auto charge = value_by_key(global_props.ion_species, ion_name)) {
+            state_->add_ion(ion_name, *charge, i.second.cv, i.second.init_iconc, i.second.init_econc, i.second.init_revpot);
         }
         else {
             throw cable_cell_error("unrecognized ion '"+ion_name+"' in mechanism");
@@ -415,10 +431,10 @@ void fvm_lowered_cell_impl<B>::initialize(
 
     target_handles.resize(mech_data.ntarget);
 
+    unsigned mech_id = 0;
     for (auto& m: mech_data.mechanisms) {
         auto& name = m.first;
         auto& config = m.second;
-        unsigned mech_id = mechanisms_.size();
 
         mechanism_layout layout;
         layout.cv = config.cv;
@@ -432,7 +448,8 @@ void fvm_lowered_cell_impl<B>::initialize(
         // contribution in the CV, and F is the scaling factor required
         // to convert from the mechanism current contribution units to A/m².
 
-        if (config.kind==mechanismKind::point) {
+        switch (config.kind) {
+        case mechanismKind::point:
             // Point mechanism contributions are in [nA]; CV area A in [µm^2].
             // F = 1/A * [nA/µm²] / [A/m²] = 1000/A.
 
@@ -452,22 +469,33 @@ void fvm_lowered_cell_impl<B>::initialize(
                     };
                 }
             }
-        }
-        else {
+            break;
+        case mechanismKind::density:
             // Current density contributions from mechanism are already in [A/m²].
 
             for (auto i: count_along(layout.cv)) {
                 layout.weight[i] = config.norm_area[i];
             }
+            break;
+        case mechanismKind::revpot:
+            // Mechanisms that set reversal potential should not be contributing
+            // to any currents, so leave weights as zero.
+            break;
         }
 
         auto minst = mech_instance(name);
-        minst.mech->instantiate(mech_id, *state_, minst.overrides, layout);
+        minst.mech->instantiate(mech_id++, *state_, minst.overrides, layout);
 
         for (auto& pv: config.param_values) {
             minst.mech->set_parameter(pv.first, pv.second);
         }
-        mechanisms_.push_back(mechanism_ptr(minst.mech.release()));
+
+        if (config.kind==mechanismKind::revpot) {
+            revpot_mechanisms_.push_back(mechanism_ptr(minst.mech.release()));
+        }
+        else {
+            mechanisms_.push_back(mechanism_ptr(minst.mech.release()));
+        }
     }
 
     // Collect detectors, probe handles.
diff --git a/arbor/include/arbor/cable_cell.hpp b/arbor/include/arbor/cable_cell.hpp
index b91a6e11f2434c10535c620a72620c6d375dcbee..f790627a60bf5a515fe7c12dd9a5847c22e1775a 100644
--- a/arbor/include/arbor/cable_cell.hpp
+++ b/arbor/include/arbor/cable_cell.hpp
@@ -5,23 +5,15 @@
 #include <vector>
 
 #include <arbor/arbexcept.hpp>
+#include <arbor/cable_cell_param.hpp>
 #include <arbor/common_types.hpp>
 #include <arbor/constants.hpp>
-#include <arbor/ion_info.hpp>
 #include <arbor/mechcat.hpp>
 #include <arbor/morphology.hpp>
 #include <arbor/segment.hpp>
 
 namespace arb {
 
-// Specialize arbor exception for errors in cell building.
-
-struct cable_cell_error: arbor_exception {
-    cable_cell_error(const std::string& what):
-        arbor_exception("cable_cell: "+what)
-    {}
-};
-
 // Location specification for point processes.
 
 struct segment_location {
@@ -64,34 +56,6 @@ struct cell_probe_address {
     probe_kind kind;
 };
 
-// Global parameter type for cell descriptions.
-
-struct cable_cell_global_properties {
-    const mechanism_catalogue* catalogue = &global_default_catalogue();
-
-    // If >0, check membrane voltage magnitude is less than limit
-    // during integration.
-    double membrane_voltage_limit_mV = 0;
-
-    // TODO: consider making some/all of the following parameters
-    // cell or even segment-local.
-    // 
-    // Consider also a model-level dictionary of default values that
-    // can be used to initialize per-cell-kind info?
-    //
-    // Defaults below chosen to match NEURON.
-
-    std::unordered_map<std::string, ion_info> ion_default = {
-        {"ca", { 2, 5e-5, 2.  }},
-        {"na", { 1, 10.,  140.}},
-        {"k",  { 1, 54.4, 2.5 }}
-    };
-
-    double temperature_K = constant::hh_squid_temp; // [K]
-    double init_membrane_potential_mV = -65; // [mV]
-    bool coalesce_synapses = true;
-};
-
 /// high-level abstract representation of a cell and its segments
 class cable_cell {
 public:
@@ -117,11 +81,14 @@ public:
         double threshold;
     };
 
+    cable_cell_parameter_set default_parameters;
+
     /// Default constructor
     cable_cell();
 
     /// Copy constructor
     cable_cell(const cable_cell& other):
+        default_parameters(other.default_parameters),
         parents_(other.parents_),
         stimuli_(other.stimuli_),
         synapses_(other.synapses_),
@@ -176,6 +143,7 @@ public:
     /// will throw an cable_cell_error exception if
     /// the cable index is not valid
     cable_segment* cable(index_type index);
+    const cable_segment* cable(index_type index) const;
 
     /// the total number of compartments over all segments
     size_type num_compartments() const;
@@ -251,6 +219,20 @@ public:
         return parents_;
     }
 
+    // Approximate per-segment mean attenuation b(f) at given frequency f,
+    // ignoring membrane resistance [1/µm].
+    value_type segment_mean_attenuation(value_type frequency, index_type segidx,
+        const cable_cell_parameter_set& global_defaults) const;
+
+    // Estimate of length constant λ(f) = 1/2 · 1/b(f), following
+    // Hines and Carnevale (2001), "NEURON: A Tool for Neuroscientists",
+    // Neuroscientist 7, pp. 123-135.
+    value_type segment_length_constant(value_type frequency, index_type segidx,
+        const cable_cell_parameter_set& global_defaults) const
+    {
+        return 0.5/segment_mean_attenuation(frequency, segidx, global_defaults);
+    }
+
 private:
     void assert_valid_segment(index_type) const;
 
diff --git a/arbor/include/arbor/cable_cell_param.hpp b/arbor/include/arbor/cable_cell_param.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2c147c0ce25667eb91ee1b0ee12e285e4cbfc754
--- /dev/null
+++ b/arbor/include/arbor/cable_cell_param.hpp
@@ -0,0 +1,157 @@
+#pragma once
+
+#include <arbor/arbexcept.hpp>
+#include <arbor/mechcat.hpp>
+#include <arbor/util/optional.hpp>
+
+#include <unordered_map>
+#include <string>
+
+namespace arb {
+
+// Specialized arbor exception for errors in cell building.
+
+struct cable_cell_error: arbor_exception {
+    cable_cell_error(const std::string& what):
+        arbor_exception("cable_cell: "+what) {}
+};
+
+// Mechanism description, viz. mechanism name and
+// (non-global) parameter settings. Used to assign
+// density and point mechanisms to segments and
+// reversal potential computations to cells.
+
+struct mechanism_desc {
+    struct field_proxy {
+        mechanism_desc* m;
+        std::string key;
+
+        field_proxy& operator=(double v) {
+            m->set(key, v);
+            return *this;
+        }
+
+        operator double() const {
+            return m->get(key);
+        }
+    };
+
+    // implicit
+    mechanism_desc(std::string name): name_(std::move(name)) {}
+    mechanism_desc(const char* name): name_(name) {}
+
+    mechanism_desc() = default;
+    mechanism_desc(const mechanism_desc&) = default;
+    mechanism_desc(mechanism_desc&&) = default;
+
+    mechanism_desc& operator=(const mechanism_desc&) = default;
+    mechanism_desc& operator=(mechanism_desc&&) = default;
+
+    mechanism_desc& set(const std::string& key, double value) {
+        param_[key] = value;
+        return *this;
+    }
+
+    double operator[](const std::string& key) const {
+        return get(key);
+    }
+
+    field_proxy operator[](const std::string& key) {
+        return {this, key};
+    }
+
+    double get(const std::string& key) const {
+        auto i = param_.find(key);
+        if (i==param_.end()) {
+            throw std::out_of_range("no field "+key+" set");
+        }
+        return i->second;
+    }
+
+    const std::unordered_map<std::string, double>& values() const {
+        return param_;
+    }
+
+    const std::string& name() const { return name_; }
+
+private:
+    std::string name_;
+    std::unordered_map<std::string, double> param_;
+};
+
+// Cable cell ion and electrical defaults.
+//
+// Parameters can be overridden with `cable_cell_local_parameter_set`
+// on unbranched segments within a cell; per-cell and global defaults
+// use `cable_cell_parameter_set`, which extends the parameter set
+// to supply per-cell or global ion reversal potential calculation
+// mechanisms.
+
+struct cable_cell_ion_data {
+    double init_int_concentration = NAN;
+    double init_ext_concentration = NAN;
+    double init_reversal_potential = NAN;
+};
+
+struct cable_cell_local_parameter_set {
+    std::unordered_map<std::string, cable_cell_ion_data> ion_data;
+    util::optional<double> init_membrane_potential; // [mV]
+    util::optional<double> temperature_K;           // [K]
+    util::optional<double> axial_resistivity;       // [Ω·cm]
+    util::optional<double> membrane_capacitance;    // [F/m²]
+};
+
+struct cable_cell_parameter_set: public cable_cell_local_parameter_set {
+    std::unordered_map<std::string, mechanism_desc> reversal_potential_method;
+
+    // We'll need something like this until C++17, for sane initialization syntax.
+    cable_cell_parameter_set() = default;
+    cable_cell_parameter_set(cable_cell_local_parameter_set p, std::unordered_map<std::string, mechanism_desc> m = {}):
+        cable_cell_local_parameter_set(std::move(p)), reversal_potential_method(std::move(m))
+    {}
+};
+
+extern cable_cell_local_parameter_set neuron_parameter_defaults;
+
+// Global cable cell data.
+
+struct cable_cell_global_properties {
+    const mechanism_catalogue* catalogue = &global_default_catalogue();
+
+    // If >0, check membrane voltage magnitude is less than limit
+    // during integration.
+    double membrane_voltage_limit_mV = 0;
+
+    // True => combine linear synapses for performance.
+    bool coalesce_synapses = true;
+
+    // Available ion species, together with charge.
+    std::unordered_map<std::string, int> ion_species = {
+        {"na", 1},
+        {"k", 1},
+        {"ca", 2}
+    };
+
+    cable_cell_parameter_set default_parameters;
+
+    // Convenience methods for adding a new ion together with default ion values.
+    void add_ion(const std::string& ion_name, int charge, double init_iconc, double init_econc, double init_revpot) {
+        ion_species[ion_name] = charge;
+
+        auto &ion_data = default_parameters.ion_data[ion_name];
+        ion_data.init_int_concentration = init_iconc;
+        ion_data.init_ext_concentration = init_econc;
+        ion_data.init_reversal_potential = init_revpot;
+    }
+
+    void add_ion(const std::string& ion_name, int charge, double init_iconc, double init_econc, mechanism_desc revpot_mechanism) {
+        add_ion(ion_name, charge, init_iconc, init_econc, 0);
+        default_parameters.reversal_potential_method[ion_name] = std::move(revpot_mechanism);
+    }
+};
+
+// Throw cable_cell_error if any default parameters are left unspecified,
+// or if the supplied ion data is incomplete.
+void check_global_properties(const cable_cell_global_properties&);
+
+} // namespace arb
diff --git a/arbor/include/arbor/ion_info.hpp b/arbor/include/arbor/ion_info.hpp
deleted file mode 100644
index a3e97263b091bc592cfdc7decc375e6fd4a24cf5..0000000000000000000000000000000000000000
--- a/arbor/include/arbor/ion_info.hpp
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma once
-
-namespace arb {
-
-struct ion_info {
-    int charge;                       // charge of ionic species
-    double default_int_concentration; // (mM) default internal concentration
-    double default_ext_concentration; // (mM) default external concentration
-};
-
-} // namespace arb
diff --git a/arbor/include/arbor/mechanism.hpp b/arbor/include/arbor/mechanism.hpp
index b6ed27f974ea9eff7400549abd211aa58f0dcf22..37e497f865e7965c8591c7d94f6f188e697f9ffb 100644
--- a/arbor/include/arbor/mechanism.hpp
+++ b/arbor/include/arbor/mechanism.hpp
@@ -9,7 +9,7 @@
 
 namespace arb {
 
-enum class mechanismKind {point, density};
+enum class mechanismKind {point, density, revpot};
 
 class mechanism;
 using mechanism_ptr = std::unique_ptr<mechanism>;
diff --git a/arbor/include/arbor/segment.hpp b/arbor/include/arbor/segment.hpp
index 1f408850f1b0757a6e30fa49d22c8343cf0f48b2..38f1849c65e0b8a8c81ba722ca5d7744f0446c38 100644
--- a/arbor/include/arbor/segment.hpp
+++ b/arbor/include/arbor/segment.hpp
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include <arbor/assert.hpp>
+#include <arbor/cable_cell_param.hpp>
 #include <arbor/common_types.hpp>
 #include <arbor/math.hpp>
 #include <arbor/morphology.hpp>
@@ -19,59 +20,6 @@
 
 namespace arb {
 
-// Mechanism information attached to a segment.
-
-struct mechanism_desc {
-    struct field_proxy {
-        mechanism_desc* m;
-        std::string key;
-
-        field_proxy& operator=(double v) {
-            m->set(key, v);
-            return *this;
-        }
-
-        operator double() const {
-            return m->get(key);
-        }
-    };
-
-    // implicit
-    mechanism_desc(std::string name): name_(std::move(name)) {}
-    mechanism_desc(const char* name): name_(name) {}
-
-    mechanism_desc& set(const std::string& key, double value) {
-        param_[key] = value;
-        return *this;
-    }
-
-    double operator[](const std::string& key) const {
-        return get(key);
-    }
-
-    field_proxy operator[](const std::string& key) {
-        return {this, key};
-    }
-
-    double get(const std::string& key) const {
-        auto i = param_.find(key);
-        if (i==param_.end()) {
-            throw std::out_of_range("no field "+key+" set");
-        }
-        return i->second;
-    }
-
-    const std::unordered_map<std::string, double>& values() const {
-        return param_;
-    }
-
-    const std::string& name() const { return name_; }
-
-private:
-    std::string name_;
-    std::unordered_map<std::string, double> param_;
-};
-
 // forward declarations of segment specializations
 class soma_segment;
 class cable_segment;
@@ -164,9 +112,7 @@ public:
         return mechanisms_;
     }
 
-    // common electrical properties
-    value_type rL = 100.0;   // resistivity [Ohm.cm]
-    value_type cm = 0.01;    // capacitance [F/m^2] : 10 nF/mm^2 = 0.01 F/m^2
+    cable_cell_local_parameter_set parameters; // override cell and global defaults
 
 protected:
     segment(section_kind kind): kind_(kind) {}
@@ -297,25 +243,6 @@ public:
         return std::accumulate(lengths_.begin(), lengths_.end(), value_type{});
     }
 
-    value_type length_constant(value_type freq_Hz) const override {
-        // Following Hine and Carnevale (2001), "NEURON: A Tool for Neuroscientists",
-        // Neuroscientist 7, pp. 123-135.
-        //
-        // λ(f) = approx. sqrt(diameter/(pi*f*rL*cm))/2.
-        //
-        // Pick smallest non-zero diameter in the segment.
-
-        value_type r_min = 0;
-        for (auto r: radii_) {
-            if (r>0 && (r_min==0 || r<r_min)) r_min = r;
-        }
-        value_type d_min = r_min*2e-6; // [m]
-        value_type rc = rL*0.01*cm;  // [s/m]
-        value_type lambda = std::sqrt(d_min/(math::pi<double>*freq_Hz*rc))/2.; // [m]
-
-        return lambda*1e6; // [µm]
-    }
-
     bool has_locations() const
     {
         return locations_.size() > 0;
diff --git a/arbor/util/maputil.hpp b/arbor/util/maputil.hpp
index afb2f3ad254b323bd5f68022a8f5b4ade883c60a..44956499488ef3d4e5828a7583fa958472638f08 100644
--- a/arbor/util/maputil.hpp
+++ b/arbor/util/maputil.hpp
@@ -121,6 +121,19 @@ optional<typename sequence_traits<C>::difference_type> binary_search_index(const
     return it!=strict.end() && key==*it? just(std::distance(strict.begin(), it)): nullopt;
 }
 
+// As binary_search_index above, but compare key against the proj(x) for elements
+// x in the sequence. The image of proj applied to the sequence must be monotonically
+// increasing.
+
+template <typename C, typename Key, typename Proj>
+optional<typename sequence_traits<C>::difference_type> binary_search_index(const C& c, const Key& key, const Proj& proj) {
+    auto strict = strict_view(c);
+    auto projected = transform_view(strict, proj);
+    auto it = std::lower_bound(projected.begin(), projected.end(), key);
+    return it!=strict.end() && key==*it? just(std::distance(projected.begin(), it)): nullopt;
+}
+
+
 // Key equality helper for NUL-terminated strings.
 
 struct cstr_equal {
diff --git a/doc/cpp_cable_cell.rst b/doc/cpp_cable_cell.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4cf1dce867ae1eeae7a954ce6afde27f96db56a6
--- /dev/null
+++ b/doc/cpp_cable_cell.rst
@@ -0,0 +1,260 @@
+.. _cppcablecell:
+
+Cable cells
+===============
+
+.. Warning::
+   The interface for building and modifying cable cell objects
+   will be thoroughly revised in the near future. The documentation
+   here is primarily a place holder.
+
+Cable cells, which use the :cpp:enum:`cell_kind` :cpp:expr:`cable`,
+represent morphologically-detailed neurons as 1-d trees, with
+electrical and biophysical properties mapped onto those trees.
+
+A single cell is represented by an object of type :cpp:type:`cable_cell`.
+Properties shared by all cable cells, as returned by the recipe
+:cpp:expr:`get_global_properties` method, are described by an
+object of type :cpp:type:`cable_cell_global_properties`.
+
+
+The :cpp:type:`cable_cell` object
+---------------------------------
+
+Cable cells are built up from a series of :cpp:type:`segment`
+objects, which themselves describe an unbranched component of the
+cell morphology. These segments are added via the methods:
+
+.. cpp:function:: soma_segment* cable_cell::add_soma(double radius)
+
+   Add the soma to the cable cell with the given radius. There
+   can be only one per cell.
+
+   The soma segment has index 0, and must be added before any
+   cable segments.
+
+.. cpp:function:: cable_segment* cable_cell::add_cable(cell_lid_type index, Args&&... args)
+
+   Add a unbranched section of the cell morphology, with its proximal
+   end attached to the segment given by :cpp:expr:`index`. The
+   following arguments are forwarded to the :cpp:type:`cable_segment`
+   constructor.
+
+Segment indices are exactly the order in which they have been added
+to a cell, counting from zero (for the soma). Both :cpp:type:`soma_segment`
+and :cpp:type:`cable_segment` are derived from the abstract base
+class :cpp:type:`segment`.
+
+.. todo::
+
+   Describe cable_segment constructor arguments, unless we get to the
+   replace cell building/morphology implementation first.
+
+Each segment will inherit the electrical properties of the cell, unless
+otherwise overriden (see below).
+
+
+Cell dynamics
+-------------
+
+Each segment in a cell may have attached to it one or more density *mechanisms*,
+which describe biophysical processes. These are processes
+that are distributed in space, but whose behaviour is defined purely
+by the state of the cell and the process at any given point.
+
+Cells may also have *point* mechanisms, which are added directly to the
+:cpp:type:`cable_cell` object.
+
+A third type of mechanism, which describes ionic reversal potential
+behaviour, can be specified for cells or the whole model via cell parameter
+settings, described below.
+
+Mechanisms are described by a :cpp:type:`mechanism_desc` object. These specify the
+name of the mechanism (used to find the mechanism in the mechanism catalogue)
+and parameter values for the mechanism that apply within a segment.
+A :cpp:type:`mechanism_desc` is effectively a wrapper around a name and
+a dictionary of parameter/value settings.
+
+Mechanism descriptions can be constructed implicitly from the
+mechanism name, and mechanism parameter values then set with the
+:cpp:expr:`set` method. Relevant :cpp:type:`mechanism_desc` methods:
+
+.. cpp:function:: mechanism_desc::mechanism_desc(std::string name)
+
+   Construct a mechanism description for the mechanism named `name`.
+
+.. cpp:function:: mechanism_desc& mechanism_desc::set(const std::string& key, double value)
+
+   Sets the parameter associated with :cpp:expr:`key` in the description.
+   Returns a reference to the mechanism description, so that calls to
+   :cpp:expr:`set` can be chained in a single expression.
+
+
+Density mechanisms are associated with a cable cell object with:
+
+.. cpp:function:: void segment::add_mechanism(mechanism_desc mech)
+
+Point mechanisms, which are associated with connection end points on a
+cable cell, are attached to a cell with:
+
+.. cpp:function:: void cable_cell::add_synapse(segment_location loc, mechanism_desc mech)
+
+where :cpp:type:`segment_location` is a simple struct holding a segment index
+and a relative position (from 0, proximal, to 1, distal) along that segment:
+
+.. cpp:function:: segment_location::segment_location(cell_lid_type index, double position)
+
+
+Electrical properities and ion values
+-------------------------------------
+
+On each cell segment, electrical and ion properties can be specified by the
+:cpp:expr:`parameters` field, of type :cpp:type:`cable_cell_local_parameter_set`.
+
+The :cpp:type:`cable_cell_local_parameter_set` has the following members,
+where an empty optional value or missing map key indicates that the corresponding
+value should be taken from the cell or global parameter set.
+
+.. cpp:class:: cable_cell_local_parameter_set
+
+   .. cpp:member:: std::unordered_map<std::string, cable_cell_ion_data> ion_data
+
+   The keys of this map are names of ions, whose parameters will be locally overriden.
+   The struct :cpp:type:`cable_cell_ion_data` has three fields:
+   :cpp:type:`init_int_concentration`, :cpp:type:`init_ext_concentration`, and
+   :cpp:type:`init_reversal_potential`.
+
+   Internal and external concentrations are given in millimolars, i.e. mol/m³.
+   Reversal potential is given in millivolts.
+
+   .. cpp:member:: util::optional<double> init_membrane_potential
+
+   Initial membrane potential in millivolts.
+
+   .. cpp:member:: util::optional<double> temperature_K
+
+   Local temperature in Kelvin.
+
+   .. cpp:member:: util::optional<double> axial_resistivity
+
+   Local resistivity of the intracellular medium, in ohm-centimetres.
+
+   .. cpp:member:: util::optional<double> membrane_capacitance
+
+   Local areal capacitance of the cell membrane, in Farads per square metre.
+
+
+Default parameters for a cell are given by the :cpp:expr:`default_parameters`
+field in the :cpp:type:`cable_cell` object. This is a value of type :cpp:type:`cable_cell_parameter_set`,
+which extends :cpp:type:`cable_cell_local_parameter_set` by adding an additional
+field describing reversal potential computation:
+
+.. cpp:class:: cable_cell_parameter_set: public cable_cell_local_parameter_set
+
+   .. cpp:member:: std::unordered_map<std::string, mechanism_desc> reversal_potential_method
+
+   Maps the name of an ion to a 'reversal potential' mechanism that describes
+   how it should be computed. When no mechanism is provided for an ionic reversal
+   potential, the reversal potential will be kept at its initial value.
+
+Default parameters for all cells are supplied in the :cpp:type:`cable_cell_global_properties`
+struct.
+
+Global properties
+-----------------
+
+.. cpp:class:: cable_cell_global_properties
+
+   .. cpp:member:: const mechanism_catalogue* catalogue
+
+   All mechanism names refer to mechanism instances in this mechanism catalogue.
+   By default, this is set to point to `global_default_catalogue()`, the catalogue
+   that contains all mechanisms bundled with Arbor.
+
+   .. cpp:member:: double membrane_voltage_limit_mV
+
+   If non-zero, check to see if the membrane voltage ever exceeds this value
+   in magnitude during the course of a simulation. If so, throw an exception
+   and abort the simulation.
+
+   .. cpp:member:: bool coalesce_synapses
+
+   When synapse dynamics are sufficiently simple, the states of synapses within
+   the same discretized element can be combined for better performance. This
+   is true by default.
+
+   .. cpp:member:: std::unordered_map<std::string, int> ion_species
+
+   Every ion species used by cable cells in the simulation must have an entry in
+   this map, which takes an ion name to its charge, expressed as a multiple of
+   the elementary charge. By default, it is set to include sodium "na" with
+   charge 1, calcium "ca" with charge 2, and potassium "k" with charge 1.
+
+   .. cpp:member:: cable_cell_parameter_set default_parameters
+
+   The default electrical and physical properties associated with each cable
+   cell, unless overridden locally. In the global properties, *every
+   optional field must be given a value*, and every ion must have its default
+   values set in :cpp:expr:`default_parameters.ion_data`.
+
+   .. cpp:function:: add_ion(const std::string& ion_name, int charge, double init_iconc, double init_econc, double init_revpot)
+
+   Convenience function for adding a new ion to the global :cpp:expr:`ion_species`
+   table, and setting up its default values in the `ion_data` table.
+
+   .. cpp:function:: add_ion(const std::string& ion_name, int charge, double init_iconc, double init_econc, mechanism_desc revpot_mechanism)
+
+   As above, but set the initial reversal potential to zero, and use the given mechanism
+   for reversal potential calculation.
+
+
+For convenience, :cpp:expr:`neuron_parameter_defaults` is a predefined :cpp:type:`cable_cell_local_parameter_set`
+value that holds values that correspond to NEURON defaults. To use these values,
+assign them to the :cpp:expr:`default_parameters` field of the global properties
+object returned in the recipe.
+
+
+Reversal potential dynamics
+---------------------------
+
+If no reversal potential mechanism is specified for an ion species, the initial
+reversal potential values are maintained for the course of a simulation. Otherwise,
+a provided mechanism does the work, but it subject to some strict restrictions.
+A reversal potential mechanism described in NMODL:
+
+* May not maintain any STATE variables.
+* Can only write to the "eX" value associated with an ion.
+* Can not given as a POINT mechanism.
+
+Essentially, reversal potential mechanisms must be pure functions of cellular
+and ionic state.
+
+If a reversal potential mechanism writes to multiple ions, then if the mechanism
+is given for one of the ions in the global or per-cell parameters, it must be
+given for all of them.
+
+Arbor's default catalogue includes a "nernst" reversal potential, which is
+parameterized over a single ion, and so can be assigned to e.g. calcium in
+the global parameters via
+
+.. code::
+
+   cable_cell_global_properties gprop;
+   // ...
+   gprop.default_parameters.reversal_potential_method["ca"] = "nernst/ca";
+
+
+This mechanism has global scalar parameters for the gas constant *R* and
+Faraday constant *F*, corresponding to the exact values given by the 2019
+redifinition of the SI base units. These values can be changed in a derived
+mechanism in order to use, for example, older values of these physical
+constants.
+
+.. code::
+
+   mechanism_catalogue mycat(global_default_catalogue());
+   mycat.derive("nernst1998", "nernst", {{"R", 8.314472}, {"F", 96485.3415}});
+
+   gprop.catalogue = &mycat;
+   gprop.default_parameters.reversal_potential_method["ca"] = "nernst1998/ca";
+
diff --git a/doc/index.rst b/doc/index.rst
index d027f7c7d082acf76d2569cfe79d34d0834a8cb9..b87dc5b1aa19b83fb6e97ee72d0396125c88de82 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -104,6 +104,7 @@ Alternative citation formats for the paper can be `downloaded here <https://ieee
    cpp_recipe
    cpp_domdec
    cpp_simulation
+   cpp_cable_cell
 
 .. toctree::
    :caption: Developers:
diff --git a/mechanisms/CMakeLists.txt b/mechanisms/CMakeLists.txt
index 3597672e3af91f650dbaaa468a40317c735fe023..8ce3653b6dc45a91e5538cd5f030fb98d6c1091b 100644
--- a/mechanisms/CMakeLists.txt
+++ b/mechanisms/CMakeLists.txt
@@ -1,7 +1,7 @@
 include(BuildModules.cmake)
 
 # The list of library-provided mechanisms used to populate the default catalogue:
-set(mechanisms pas hh expsyn exp2syn test_kin1 test_kinlva test_ca nax kdrmt kamt)
+set(mechanisms pas hh expsyn exp2syn nernst test_kin1 test_kinlva test_ca nax kdrmt kamt)
 
 set(mod_srcdir "${CMAKE_CURRENT_SOURCE_DIR}/mod")
 
diff --git a/mechanisms/mod/nernst.mod b/mechanisms/mod/nernst.mod
new file mode 100644
index 0000000000000000000000000000000000000000..aca2a7170ff23933f143804e72d02491863b5ca1
--- /dev/null
+++ b/mechanisms/mod/nernst.mod
@@ -0,0 +1,33 @@
+: Compute reversal potential for generic ion using Nernst equation.
+:
+: TODO: support global derived parameters via e.g. GLOBAL + ASSIGNED,
+: so that we don't need to store a copy of coeff at every CV.
+
+NEURON {
+    SUFFIX nernst
+    USEION x READ xi, xo WRITE ex VALENCE zx
+    GLOBAL R, F
+    RANGE coeff
+}
+
+PARAMETER {
+    R = 8.31446261815324 (joule/kelvin/mole) : gas constant
+    F = 96485.3321233100184 (coulomb/mole) : Faraday's constant
+    celsius : temperature in °C (set externally)
+}
+
+ASSIGNED {
+    coeff
+}
+
+INITIAL {
+    coeff = R*(celsius+273.15)/(zx*F)*1000
+}
+
+STATE {
+}
+
+BREAKPOINT  {
+    ex = coeff*log(xo/xi)
+}
+
diff --git a/modcc/blocks.hpp b/modcc/blocks.hpp
index 5d9d1cf85fcbb52a31966927d8834f4b42b1e8e4..a2c098aa5e81cbc0dd6df3ec5bb64243a5126c27 100644
--- a/modcc/blocks.hpp
+++ b/modcc/blocks.hpp
@@ -34,6 +34,9 @@ struct IonDep {
     bool uses_concentration_ext() const {
         return has_variable(name+"o");
     };
+    bool writes_current() const {
+        return writes_variable("i"+name);
+    };
     bool writes_concentration_int() const {
         return writes_variable(name+"i");
     };
diff --git a/modcc/identifier.hpp b/modcc/identifier.hpp
index 8e62534a999740c71d76a280c90c25cd702ca329..d1c900c978606bc7f45434df4f6cfa6f3c66a011 100644
--- a/modcc/identifier.hpp
+++ b/modcc/identifier.hpp
@@ -5,7 +5,7 @@
 #include <stdexcept>
 
 enum class moduleKind {
-    point, density
+    point, density, revpot
 };
 
 /// indicate how a variable is accessed
@@ -117,7 +117,7 @@ inline sourceKind ion_source(const std::string& ion, const std::string& var, mod
     else if (var=="i"+ion) return mkind==moduleKind::point? sourceKind::ion_current: sourceKind::ion_current_density;
     else if (var=="e"+ion) return sourceKind::ion_revpot;
     else if (var==ion+"i") return sourceKind::ion_iconc;
-    else if (var==ion+"e") return sourceKind::ion_econc;
+    else if (var==ion+"o") return sourceKind::ion_econc;
     else return sourceKind::no_source;
 }
 
diff --git a/modcc/module.cpp b/modcc/module.cpp
index 0e1ba7674b124c47fe6dd696359bc509735b7613..86a3ebec8777c7e8e706f86e046bb23563269143 100644
--- a/modcc/module.cpp
+++ b/modcc/module.cpp
@@ -447,6 +447,12 @@ bool Module::semantic() {
     }
     linear_ = linear;
 
+    // Are we writing an ionic reversal potential? If so, change the moduleKind to
+    // `revpot` and assert that the mechanism is 'pure': it has no state variables;
+    // it contributes to no currents, ionic or otherwise; it isn't a point mechanism;
+    // and it writes to only one ionic reversal potential.
+    check_revpot_mechanism();
+
     // Perform semantic analysis and inlining on function and procedure bodies
     // in order to inline calls inside the newly crated API methods.
     semantic_func_proc();
@@ -765,3 +771,43 @@ int Module::semantic_func_proc() {
     return errors;
 }
 
+void Module::check_revpot_mechanism() {
+    int n_write_revpot = 0;
+    for (auto& iondep: neuron_block_.ions) {
+        if (iondep.writes_rev_potential()) ++n_write_revpot;
+    }
+
+    if (n_write_revpot==0) return;
+
+    bool pure = true;
+
+    // Are we marked as a point mechanism?
+    if (kind()==moduleKind::point) {
+        error("Cannot write reversal potential from a point mechanism.");
+        return;
+    }
+
+    // Do we write any other ionic variables or a nonspecific current?
+    for (auto& iondep: neuron_block_.ions) {
+        pure &= !iondep.writes_concentration_int();
+        pure &= !iondep.writes_concentration_ext();
+        pure &= !iondep.writes_current();
+    }
+    pure &= !neuron_block_.has_nonspecific_current();
+
+    if (!pure) {
+        error("Cannot write reversal potential and also to other currents or ionic state.");
+        return;
+    }
+
+    // Do we have any state variables?
+    pure &= state_block_.state_variables.size()==0;
+    if (!pure) {
+        error("Cannot write reversal potential and also maintain state variables.");
+        return;
+    }
+
+    kind_ = moduleKind::revpot;
+}
+
+
diff --git a/modcc/module.hpp b/modcc/module.hpp
index 4cf5e6ac5a1a0b8727a0c09042e5d1c9f8e631ff..09e29d407594c1f3ef2201ca364083f065e185c7 100644
--- a/modcc/module.hpp
+++ b/modcc/module.hpp
@@ -139,6 +139,9 @@ private:
         return s == symbols_.end() ? false : s->second->kind() == kind;
     }
 
+    // Check requirements for reversal potential setters.
+    void check_revpot_mechanism();
+
     // Perform semantic analysis on functions and procedures.
     // Returns the number of errors that were encountered.
     int semantic_func_proc();
diff --git a/python/cells.cpp b/python/cells.cpp
index 26f86c22a4650f2d1774deb96fddb95d2a3ac698..ed332548bada6508f605b220fb15fc3bdeb5e1d0 100644
--- a/python/cells.cpp
+++ b/python/cells.cpp
@@ -86,9 +86,10 @@ double interp(const std::array<T,2>& r, unsigned i, unsigned n) {
 arb::cable_cell make_cable_cell(arb::cell_gid_type gid, const cell_parameters& params) {
     arb::cable_cell cell;
 
+    cell.default_parameters.axial_resistivity = 100;
+
     // Add soma.
     auto soma = cell.add_soma(12.6157/2.0); // For area of 500 μm².
-    soma->rL = 100;
     soma->add_mechanism("hh");
 
     std::vector<std::vector<unsigned>> levels;
@@ -117,7 +118,6 @@ arb::cable_cell make_cable_cell(arb::cell_gid_type gid, const cell_parameters& p
                     auto dend = cell.add_cable(sec, arb::section_kind::dendrite, dend_radius, dend_radius, l);
                     dend->set_compartments(nc);
                     dend->add_mechanism("pas");
-                    dend->rL = 100;
                 }
             }
         }
diff --git a/python/recipe.hpp b/python/recipe.hpp
index f8ccaef3448481c78ff38efc453491ccdb21506b..3ee71adbb2004e4c374436643869cd5f97581416 100644
--- a/python/recipe.hpp
+++ b/python/recipe.hpp
@@ -7,6 +7,7 @@
 #include <pybind11/stl.h>
 
 #include <arbor/event_generator.hpp>
+#include <arbor/cable_cell_param.hpp>
 #include <arbor/recipe.hpp>
 
 namespace pyarb {
@@ -150,6 +151,11 @@ public:
 
     // TODO: wrap
     arb::util::any get_global_properties(arb::cell_kind kind) const override {
+        if (kind==arb::cell_kind::cable) {
+            arb::cable_cell_global_properties gprop;
+            gprop.default_parameters = arb::neuron_parameter_defaults;
+            return gprop;
+        }
         return arb::util::any{};
     }
 };
diff --git a/test/common_cells.hpp b/test/common_cells.hpp
index ae15233e463645caec0ae67c0f74af6919dad4c4..0694bf6daf874a97904bde52815fb77b5dd52eab 100644
--- a/test/common_cells.hpp
+++ b/test/common_cells.hpp
@@ -249,11 +249,12 @@ inline cable_cell make_cell_ball_and_3stick(bool with_stim = true) {
 inline cable_cell make_cell_simple_cable(bool with_stim = true) {
     cable_cell c;
 
+    c.default_parameters.axial_resistivity = 100;
+    c.default_parameters.membrane_capacitance = 0.01;
+
     c.add_soma(0);
     c.add_cable(0, section_kind::dendrite, 0.5, 0.5, 1000);
 
-    double r_L  = 100;
-    double c_m  = 0.01;
     double gbar = 0.000025;
     double I = 0.1;
 
@@ -262,8 +263,6 @@ inline cable_cell make_cell_simple_cable(bool with_stim = true) {
 
     for (auto& seg: c.segments()) {
         seg->add_mechanism(pas);
-        seg->rL = r_L;
-        seg->cm = c_m;
 
         if (seg->is_dendrite()) {
             seg->set_compartments(4);
diff --git a/test/simple_recipes.hpp b/test/simple_recipes.hpp
index e6da789a09a8b0bf347d4fbb1c1a825ef13c637a..331148055ce581197a6e83bd6bfa575fe50f8aa6 100644
--- a/test/simple_recipes.hpp
+++ b/test/simple_recipes.hpp
@@ -7,6 +7,7 @@
 
 #include <arbor/event_generator.hpp>
 #include <arbor/cable_cell.hpp>
+#include <arbor/cable_cell_param.hpp>
 #include <arbor/recipe.hpp>
 
 namespace arb {
@@ -20,6 +21,7 @@ public:
         catalogue_(global_default_catalogue())
     {
         cell_gprop_.catalogue = &catalogue_;
+        cell_gprop_.default_parameters = neuron_parameter_defaults;
     }
 
     cell_size_type num_probes(cell_gid_type i) const override {
@@ -50,8 +52,12 @@ public:
         return catalogue_;
     }
 
-    void add_ion(const char* name, int charge, double iconc, double econc) {
-        cell_gprop_.ion_default[name] = {charge, iconc, econc};
+    void add_ion(const std::string& ion_name, int charge, double init_iconc, double init_econc, double init_revpot) {
+        cell_gprop_.add_ion(ion_name, charge, init_iconc, init_econc, init_revpot);
+    }
+
+    void nernst_ion(const std::string& ion_name) {
+        cell_gprop_.default_parameters.reversal_potential_method[ion_name] = "nernst/"+ion_name;
     }
 
 protected:
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 36e66d6f508be26d9a1e35c0ed5b9cd40a968398..45d8b30b01036e3690f5f8b69d476d68d9f8783f 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -7,6 +7,9 @@ set(test_mechanisms
     linear_ca_conc
     test_cl_valence
     test_ca_read_valence
+    read_eX
+    write_multiple_eX
+    write_eX
 )
 
 include(${PROJECT_SOURCE_DIR}/mechanisms/BuildModules.cmake)
diff --git a/test/unit/mod/read_eX.mod b/test/unit/mod/read_eX.mod
new file mode 100644
index 0000000000000000000000000000000000000000..072334bff4f2d58220b282605640d9f5b911a605
--- /dev/null
+++ b/test/unit/mod/read_eX.mod
@@ -0,0 +1,23 @@
+: Test mechanism for consuming ionic reversal potential
+
+NEURON {
+    SUFFIX read_eX
+    USEION x READ ex
+}
+
+PARAMETER {}
+
+ASSIGNED {}
+
+STATE {
+    record_ex
+}
+
+INITIAL {
+    record_ex = ex
+}
+
+BREAKPOINT  {
+    record_ex = ex
+}
+
diff --git a/test/unit/mod/write_eX.mod b/test/unit/mod/write_eX.mod
new file mode 100644
index 0000000000000000000000000000000000000000..62302eb980ddb9074fbc679d2f123a002beceb88
--- /dev/null
+++ b/test/unit/mod/write_eX.mod
@@ -0,0 +1,22 @@
+: Reversal potential writer that writes to a single ion
+: as a simple function of internal concentration.
+
+NEURON {
+    SUFFIX write_eX
+    USEION x READ xi WRITE ex
+}
+
+PARAMETER {}
+
+ASSIGNED {}
+
+STATE {}
+
+INITIAL {
+    ex = 100 + xi
+}
+
+BREAKPOINT  {
+    ex = 100 + xi
+}
+
diff --git a/test/unit/mod/write_multiple_eX.mod b/test/unit/mod/write_multiple_eX.mod
new file mode 100644
index 0000000000000000000000000000000000000000..a48c571a3910e759386c1e31b0b460c7b7601843
--- /dev/null
+++ b/test/unit/mod/write_multiple_eX.mod
@@ -0,0 +1,25 @@
+: Reversal potential writer that writes to two different ions
+: as a simple function of internal concentration.
+
+NEURON {
+    SUFFIX write_multiple_eX
+    USEION x READ xi WRITE ex
+    USEION y READ yi WRITE ey
+}
+
+PARAMETER {}
+
+ASSIGNED {}
+
+STATE {}
+
+INITIAL {
+    ex = 100 + xi
+    ey = 200 + yi
+}
+
+BREAKPOINT  {
+    ex = 100 + xi
+    ey = 200 + yi
+}
+
diff --git a/test/unit/test_fvm_layout.cpp b/test/unit/test_fvm_layout.cpp
index ae394dd5f0158f6c7f607bca015da74f44c12cb3..3bd981cd580c884e1dd9fcbaef2af08f4785fce1 100644
--- a/test/unit/test_fvm_layout.cpp
+++ b/test/unit/test_fvm_layout.cpp
@@ -94,23 +94,24 @@ namespace {
         cable_cell c2;
         segment* s;
 
+        c2.default_parameters.axial_resistivity = 90;
+
         s = c2.add_soma(14./2);
         s->add_mechanism("hh");
 
         s = c2.add_cable(0, section_kind::dendrite, 1.0/2, 1.0/2, 200);
-        s->cm = 0.017;
+        s->parameters.membrane_capacitance = 0.017;
 
         s = c2.add_cable(1, section_kind::dendrite, 0.8/2, 0.8/2, 300);
-        s->cm = 0.013;
+        s->parameters.membrane_capacitance = 0.013;
 
         s = c2.add_cable(1, section_kind::dendrite, 0.7/2, 0.7/2, 180);
-        s->cm = 0.018;
+        s->parameters.membrane_capacitance = 0.018;
 
         c2.add_stimulus({2,1}, {5.,  80., 0.45});
         c2.add_stimulus({3,1}, {40., 10.,-0.2});
 
         for (auto& seg: c2.segments()) {
-            seg->rL = 90.;
             if (seg->is_dendrite()) {
                 seg->add_mechanism("pas");
                 seg->set_compartments(4);
@@ -134,7 +135,7 @@ TEST(fvm_layout, topology) {
     std::vector<cable_cell> cells = two_cell_system();
     check_two_cell_system(cells);
 
-    fvm_discretization D = fvm_discretize(cells);
+    fvm_discretization D = fvm_discretize(cells, neuron_parameter_defaults);
 
     // Expected CV layouts for cells, segment indices in paren.
     //
@@ -215,7 +216,7 @@ TEST(fvm_layout, area) {
     std::vector<cable_cell> cells = two_cell_system();
     check_two_cell_system(cells);
 
-    fvm_discretization D = fvm_discretize(cells);
+    fvm_discretization D = fvm_discretize(cells, neuron_parameter_defaults);
 
     // Note: stick models have constant diameter segments.
     // Refer to comment above for CV vs. segment layout.
@@ -256,14 +257,13 @@ TEST(fvm_layout, area) {
     // capacitance from segments 3, 4 and 5 (cell 1 segments
     // 1, 2 and 3 respectively).
 
-    double cm1 = cells[1].segment(1)->cm;
-    double cm2 = cells[1].segment(2)->cm;
-    double cm3 = cells[1].segment(3)->cm;
+    double cm1 = 0.017, cm2 = 0.013, cm3 = 0.018;
 
     double c = A[3]/(2*n)*cm1+A[4]/(2*n)*cm2+A[5]/(2*n)*cm3;
     EXPECT_FLOAT_EQ(c, D.cv_capacitance[11]);
 
-    double cm0 = cells[1].soma()->cm;
+    //double cm0 = cells[1].soma()->cm;
+    double cm0 = neuron_parameter_defaults.membrane_capacitance.value();
     c = A[2]*cm0;
     EXPECT_FLOAT_EQ(c, D.cv_capacitance[6]);
 
@@ -276,8 +276,9 @@ TEST(fvm_layout, area) {
     double a = volume(cable)/cable->length();
     EXPECT_FLOAT_EQ(math::pi<double>*0.8*0.8/4, a);
 
+    double rL = 90;
     double h = cable->length()/4;
-    double g = a/h/cable->rL; // [µm·S/cm]
+    double g = a/h/rL; // [µm·S/cm]
     g *= 100; // [µS]
 
     EXPECT_FLOAT_EQ(g, D.face_conductance[13]);
@@ -294,7 +295,9 @@ TEST(fvm_layout, mech_index) {
     cells[1].add_synapse({3, 0.4}, "expsyn");
 
     cable_cell_global_properties gprop;
-    fvm_discretization D = fvm_discretize(cells);
+    gprop.default_parameters = neuron_parameter_defaults;
+
+    fvm_discretization D = fvm_discretize(cells, gprop.default_parameters);
     fvm_mechanism_data M = fvm_build_mechanism_data(gprop, cells, D);
 
     auto& hh_config = M.mechanisms.at("hh");
@@ -353,9 +356,11 @@ TEST(fvm_layout, coalescing_synapses) {
     };
 
     cable_cell_global_properties gprop_no_coalesce;
+    gprop_no_coalesce.default_parameters = neuron_parameter_defaults;
     gprop_no_coalesce.coalesce_synapses = false;
 
     cable_cell_global_properties gprop_coalesce;
+    gprop_coalesce.default_parameters = neuron_parameter_defaults;
     gprop_coalesce.coalesce_synapses = true;
 
     {
@@ -367,7 +372,7 @@ TEST(fvm_layout, coalescing_synapses) {
         cell.add_synapse({1, 0.7}, "expsyn");
         cell.add_synapse({1, 0.9}, "expsyn");
 
-        fvm_discretization D = fvm_discretize({cell});
+        fvm_discretization D = fvm_discretize({cell}, neuron_parameter_defaults);
         fvm_mechanism_data M = fvm_build_mechanism_data(gprop_coalesce, {cell}, D);
 
         auto &expsyn_config = M.mechanisms.at("expsyn");
@@ -383,7 +388,7 @@ TEST(fvm_layout, coalescing_synapses) {
         cell.add_synapse({1, 0.7}, "expsyn");
         cell.add_synapse({1, 0.9}, "exp2syn");
 
-        fvm_discretization D = fvm_discretize({cell});
+        fvm_discretization D = fvm_discretize({cell}, neuron_parameter_defaults);
         fvm_mechanism_data M = fvm_build_mechanism_data(gprop_coalesce, {cell}, D);
 
         auto &expsyn_config = M.mechanisms.at("expsyn");
@@ -402,7 +407,7 @@ TEST(fvm_layout, coalescing_synapses) {
         cell.add_synapse({1, 0.7}, "expsyn");
         cell.add_synapse({1, 0.9}, "expsyn");
 
-        fvm_discretization D = fvm_discretize({cell});
+        fvm_discretization D = fvm_discretize({cell}, neuron_parameter_defaults);
         fvm_mechanism_data M = fvm_build_mechanism_data(gprop_no_coalesce, {cell}, D);
 
         auto &expsyn_config = M.mechanisms.at("expsyn");
@@ -418,7 +423,7 @@ TEST(fvm_layout, coalescing_synapses) {
         cell.add_synapse({1, 0.7}, "expsyn");
         cell.add_synapse({1, 0.9}, "exp2syn");
 
-        fvm_discretization D = fvm_discretize({cell});
+        fvm_discretization D = fvm_discretize({cell}, neuron_parameter_defaults);
         fvm_mechanism_data M = fvm_build_mechanism_data(gprop_no_coalesce, {cell}, D);
 
         auto &expsyn_config = M.mechanisms.at("expsyn");
@@ -438,7 +443,7 @@ TEST(fvm_layout, coalescing_synapses) {
         cell.add_synapse({1, 0.7}, "expsyn");
         cell.add_synapse({1, 0.7}, "expsyn");
 
-        fvm_discretization D = fvm_discretize({cell});
+        fvm_discretization D = fvm_discretize({cell}, neuron_parameter_defaults);
         fvm_mechanism_data M = fvm_build_mechanism_data(gprop_coalesce, {cell}, D);
 
         auto &expsyn_config = M.mechanisms.at("expsyn");
@@ -454,7 +459,7 @@ TEST(fvm_layout, coalescing_synapses) {
         cell.add_synapse({1, 0.3}, syn_desc("expsyn", 0.1, 0.2));
         cell.add_synapse({1, 0.7}, syn_desc("expsyn", 0.1, 0.2));
 
-        fvm_discretization D = fvm_discretize({cell});
+        fvm_discretization D = fvm_discretize({cell}, neuron_parameter_defaults);
         fvm_mechanism_data M = fvm_build_mechanism_data(gprop_coalesce, {cell}, D);
 
         auto &expsyn_config = M.mechanisms.at("expsyn");
@@ -476,7 +481,7 @@ TEST(fvm_layout, coalescing_synapses) {
         cell.add_synapse({1, 0.3}, syn_desc("expsyn", 0, 2));
         cell.add_synapse({1, 0.3}, syn_desc("expsyn", 1, 2));
 
-        fvm_discretization D = fvm_discretize({cell});
+        fvm_discretization D = fvm_discretize({cell}, neuron_parameter_defaults);
         fvm_mechanism_data M = fvm_build_mechanism_data(gprop_coalesce, {cell}, D);
 
         auto &expsyn_config = M.mechanisms.at("expsyn");
@@ -501,7 +506,7 @@ TEST(fvm_layout, coalescing_synapses) {
         cell.add_synapse({1, 0.7}, syn_desc_2("exp2syn", 2, 1));
         cell.add_synapse({1, 0.7}, syn_desc_2("exp2syn", 2, 2));
 
-        fvm_discretization D = fvm_discretize({cell});
+        fvm_discretization D = fvm_discretize({cell}, neuron_parameter_defaults);
         fvm_mechanism_data M = fvm_build_mechanism_data(gprop_coalesce, {cell}, D);
 
         auto &expsyn_config = M.mechanisms.at("expsyn");
@@ -548,7 +553,9 @@ TEST(fvm_layout, synapse_targets) {
     cells[1].add_synapse({3, 0.7}, syn_desc("exp2syn", 6));
 
     cable_cell_global_properties gprop;
-    fvm_discretization D = fvm_discretize(cells);
+    gprop.default_parameters = neuron_parameter_defaults;
+
+    fvm_discretization D = fvm_discretize(cells, gprop.default_parameters);
     fvm_mechanism_data M = fvm_build_mechanism_data(gprop, cells, D);
 
     ASSERT_EQ(1u, M.mechanisms.count("expsyn"));
@@ -585,9 +592,6 @@ TEST(fvm_layout, synapse_targets) {
 }
 
 
-// TODO: migrate tests for proportional parameter setting.
-
-
 namespace {
     double wm_impl(double wa, double xa) {
         return wa? xa/wa: 0;
@@ -708,7 +712,9 @@ TEST(fvm_layout, density_norm_area) {
     expected_gl[10] = seg3_gl;
 
     cable_cell_global_properties gprop;
-    fvm_discretization D = fvm_discretize(cells);
+    gprop.default_parameters = neuron_parameter_defaults;
+
+    fvm_discretization D = fvm_discretize(cells, gprop.default_parameters);
     fvm_mechanism_data M = fvm_build_mechanism_data(gprop, cells, D);
 
     // Check CV area assumptions.
@@ -747,19 +753,26 @@ TEST(fvm_layout, valence_verify) {
     auto soma = c.add_soma(6);
 
     soma->add_mechanism("test_cl_valence");
-    fvm_discretization D = fvm_discretize(cells);
 
-    mechanism_catalogue testcat = make_unit_test_catalogue();
     cable_cell_global_properties gprop;
-    gprop.catalogue = &testcat;
+    gprop.default_parameters = neuron_parameter_defaults;
 
-    EXPECT_THROW(fvm_build_mechanism_data(gprop, cells, D), cable_cell_error);
+    fvm_discretization D = fvm_discretize(cells, neuron_parameter_defaults);
+
+    mechanism_catalogue testcat = make_unit_test_catalogue();
+    gprop.catalogue = &testcat;
 
-    gprop.ion_default["cl"] = { -2, 1., 1. };
+    // Missing the 'cl' ion:
     EXPECT_THROW(fvm_build_mechanism_data(gprop, cells, D), cable_cell_error);
 
-    gprop.ion_default["cl"] = { -1, 1., 1. };
+    // Adding ion, should be fine now:
+    gprop.default_parameters.ion_data["cl"] = { 1., 1., 0. };
+    gprop.ion_species["cl"] = -1;
     EXPECT_NO_THROW(fvm_build_mechanism_data(gprop, cells, D));
+
+    // 'cl' ion has wrong charge:
+    gprop.ion_species["cl"] = -2;
+    EXPECT_THROW(fvm_build_mechanism_data(gprop, cells, D), cable_cell_error);
 }
 
 TEST(fvm_layout, ion_weights) {
@@ -809,13 +822,24 @@ TEST(fvm_layout, ion_weights) {
         {0}, {0, 2, 3}, {2, 3, 4}, {0, 1, 2, 3, 4}, {2, 4}
     };
 
-    fvec expected_iconc_norm_area[] = {
+    fvec expected_init_iconc[] = {
         {0.}, {0., 1./2, 0.}, {1./4, 0., 0.}, {0., 0., 0., 0., 0.}, {3./4, 0.}
     };
 
     cable_cell_global_properties gprop;
+    gprop.default_parameters = neuron_parameter_defaults;
+
+    fvm_value_type cai = gprop.default_parameters.ion_data["ca"].init_int_concentration;
+    fvm_value_type cao = gprop.default_parameters.ion_data["ca"].init_ext_concentration;
+
+    for (auto& v: expected_init_iconc) {
+        for (auto& iconc: v) {
+            iconc *= cai;
+        }
+    }
 
     for (auto run: count_along(mech_segs)) {
+        SCOPED_TRACE("run "+std::to_string(run));
         std::vector<cable_cell> cells(1);
         cable_cell& c = cells[0];
         construct_cell(c);
@@ -824,7 +848,7 @@ TEST(fvm_layout, ion_weights) {
             c.segments()[i]->add_mechanism("test_ca");
         }
 
-        fvm_discretization D = fvm_discretize(cells);
+        fvm_discretization D = fvm_discretize(cells, gprop.default_parameters);
         fvm_mechanism_data M = fvm_build_mechanism_data(gprop, cells, D);
 
         ASSERT_EQ(1u, M.ions.count("ca"s));
@@ -832,8 +856,84 @@ TEST(fvm_layout, ion_weights) {
 
         EXPECT_EQ(expected_ion_cv[run], ca.cv);
 
-        EXPECT_TRUE(testing::seq_almost_eq<fvm_value_type>(expected_iconc_norm_area[run], ca.iconc_norm_area));
+        EXPECT_TRUE(testing::seq_almost_eq<fvm_value_type>(expected_init_iconc[run], ca.init_iconc));
 
-        EXPECT_TRUE(util::all_of(ca.econc_norm_area, [](fvm_value_type v) { return v==1.; }));
+        EXPECT_TRUE(util::all_of(ca.init_econc, [cao](fvm_value_type v) { return v==cao; }));
     }
 }
+
+TEST(fvm_layout, revpot) {
+    // Create two cells with three ions 'a', 'b' and 'c'.
+    // Configure a reversal potential mechanism that writes to 'a' and
+    // another that writes to 'b' and 'c'.
+    //
+    // Confirm:
+    //     * Inconsistencies between revpot mech assignments are caught at discretization.
+    //     * Reversal potential mechanisms are only extended where there exists another
+    //       mechanism that reads them.
+
+    auto construct_cell = [](cable_cell& c) {
+        c.add_soma(5);
+
+        c.add_cable(0, section_kind::dendrite, 0.5, 0.5, 100);
+        c.add_cable(1, section_kind::dendrite, 0.5, 0.5, 200);
+        c.add_cable(1, section_kind::dendrite, 0.5, 0.5, 100);
+
+        for (auto& s: c.segments()) s->set_compartments(1);
+
+        // Read ea everywhere, ec only on soma.
+        for (auto& s: c.segments()) s->add_mechanism("read_eX/a");
+        c.soma()->add_mechanism("read_eX/c");
+    };
+
+    mechanism_catalogue testcat = make_unit_test_catalogue();
+
+    std::vector<cable_cell> cells(2);
+    for (auto& c: cells) construct_cell(c);
+
+    cable_cell_global_properties gprop;
+    gprop.default_parameters = neuron_parameter_defaults;
+    gprop.catalogue = &testcat;
+
+    gprop.ion_species = {{"a", 1}, {"b", 2}, {"c", 3}};
+    gprop.add_ion("a", 1, 10., 0, 0);
+    gprop.add_ion("b", 2, 30., 0, 0);
+    gprop.add_ion("c", 3, 50., 0, 0);
+
+    gprop.default_parameters.reversal_potential_method["a"] = "write_eX/a";
+    mechanism_desc write_eb_ec = "write_multiple_eX/x=b,y=c";
+
+    {
+        // need to specify ion "c" as well.
+        auto test_gprop = gprop;
+        test_gprop.default_parameters.reversal_potential_method["b"] = write_eb_ec;
+
+        fvm_discretization D = fvm_discretize(cells, test_gprop.default_parameters);
+        EXPECT_THROW(fvm_build_mechanism_data(test_gprop, cells, D), cable_cell_error);
+    }
+
+    {
+        // conflict with ion "c" on second cell.
+        auto test_gprop = gprop;
+        test_gprop.default_parameters.reversal_potential_method["b"] = write_eb_ec;
+        test_gprop.default_parameters.reversal_potential_method["c"] = write_eb_ec;
+        cells[1].default_parameters.reversal_potential_method["c"] = "write_eX/c";
+
+        fvm_discretization D = fvm_discretize(cells, test_gprop.default_parameters);
+        EXPECT_THROW(fvm_build_mechanism_data(test_gprop, cells, D), cable_cell_error);
+    }
+
+    auto& cell1_prop = cells[1].default_parameters;
+    cell1_prop.reversal_potential_method.clear();
+    cell1_prop.reversal_potential_method["b"] = write_eb_ec;
+    cell1_prop.reversal_potential_method["c"] = write_eb_ec;
+
+    fvm_discretization D = fvm_discretize(cells, gprop.default_parameters);
+    fvm_mechanism_data M = fvm_build_mechanism_data(gprop, cells, D);
+
+    // Only CV which needs write_multiple_eX/x=b,y=c is the soma (first CV)
+    // of the second cell.
+    auto soma1_index = D.cell_cv_bounds[1];
+    ASSERT_EQ(1u, M.mechanisms.count(write_eb_ec.name()));
+    EXPECT_EQ((std::vector<fvm_index_type>(1, soma1_index)), M.mechanisms.at(write_eb_ec.name()).cv);
+}
diff --git a/test/unit/test_fvm_lowered.cpp b/test/unit/test_fvm_lowered.cpp
index 7bd1b18cea57dddbb1b5d188178ff9bf44244551..42726b6b15511743a58fdec44518521cc4f23fc9 100644
--- a/test/unit/test_fvm_lowered.cpp
+++ b/test/unit/test_fvm_lowered.cpp
@@ -81,7 +81,6 @@ ACCESS_BIND(\
     private_ion_index_table_ptr,\
     &arb::multicore::mechanism::ion_index_table)
 
-
 using namespace arb;
 
 class gap_recipe_0: public recipe {
@@ -336,7 +335,10 @@ TEST(fvm_lowered, stimulus) {
     // as during the stimulus windows.
     std::vector<fvm_index_type> cell_to_intdom(cells.size(), 0);
 
-    fvm_discretization D = fvm_discretize(cells);
+    cable_cell_global_properties gprop;
+    gprop.default_parameters = neuron_parameter_defaults;
+
+    fvm_discretization D = fvm_discretize(cells, gprop.default_parameters);
     const auto& A = D.cv_area;
 
     std::vector<target_handle> targets;
@@ -537,8 +539,8 @@ TEST(fvm_lowered, read_valence) {
         rec.catalogue() = make_unit_test_catalogue();
 
         rec.catalogue().derive("na_read_valence", "test_ca_read_valence", {}, {{"ca", "na"}});
-        rec.catalogue().derive("cr_read_valence", "na_read_valence", {}, {{"na", "cr"}});
-        rec.add_ion("cr", 7, 0, 0);
+        rec.catalogue().derive("cr_read_valence", "na_read_valence", {}, {{"na", "mn"}});
+        rec.add_ion("mn", 7, 0, 0, 0);
 
         fvm_cell fvcell(context);
         fvcell.initialize({0}, rec, cell_to_intdom, targets, probe_map);
@@ -649,12 +651,12 @@ TEST(fvm_lowered, weighted_write_ion) {
     //   - Soma (segment 0) plus three dendrites (1, 2, 3) meeting at a branch point.
     //   - Dendritic segments are given 1 compartments each.
     //
-    //         /
-    //        d2
-    //       /
+    //          /
+    //         d2
+    //        /
     //   s0-d1
-    //       \.
-    //        d3
+    //        \.
+    //         d3
     //
     // The CV corresponding to the branch point should comprise the terminal
     // 1/2 of segment 1 and the initial 1/2 of segments 2 and 3.
@@ -666,7 +668,7 @@ TEST(fvm_lowered, weighted_write_ion) {
     //   dend 3: 100 µm long, 1 µm diameter cylinder
     //
     // The radius of the soma is chosen such that the surface area of soma is
-    // the same as a 100µm dendrite, which makes it easier to describe the
+    // the same as a 100 µm dendrite, which makes it easier to describe the
     // expected weights.
 
     execution_context context;
@@ -689,26 +691,27 @@ TEST(fvm_lowered, weighted_write_ion) {
     // Ca ion writer test_ca on CV 2 and 4 via segment 3:
     c.segments()[3] ->add_mechanism("test_ca");
 
+    cable1d_recipe rec(c);
+    rec.add_ion("ca", 2, con_int, con_ext, 0.0);
+
     std::vector<target_handle> targets;
     std::vector<fvm_index_type> cell_to_intdom;
     probe_association_map<probe_handle> probe_map;
 
     fvm_cell fvcell(context);
-    fvcell.initialize({0}, cable1d_recipe(c), cell_to_intdom, targets, probe_map);
+    fvcell.initialize({0}, rec, cell_to_intdom, targets, probe_map);
 
     auto& state = *(fvcell.*private_state_ptr).get();
     auto& ion = state.ion_data.at("ca"s);
-    ion.default_int_concentration = con_int;
-    ion.default_ext_concentration = con_ext;
     ion.init_concentration();
 
     std::vector<unsigned> ion_nodes = util::assign_from(ion.node_index_);
     std::vector<unsigned> expected_ion_nodes = {2, 3, 4};
     EXPECT_EQ(expected_ion_nodes, ion_nodes);
 
-    std::vector<double> ion_iconc_weights = util::assign_from(ion.weight_Xi_);
-    std::vector<double> expected_ion_iconc_weights = {0.75, 1., 0};
-    EXPECT_EQ(expected_ion_iconc_weights, ion_iconc_weights);
+    std::vector<double> ion_init_iconc = util::assign_from(ion.init_Xi_);
+    std::vector<double> expected_init_iconc = {0.75*con_int, 1.*con_int, 0};
+    EXPECT_EQ(expected_init_iconc, ion_init_iconc);
 
     auto test_ca = dynamic_cast<multicore::mechanism*>(find_mechanism(fvcell, "test_ca"));
 
@@ -721,13 +724,15 @@ TEST(fvm_lowered, weighted_write_ion) {
     auto& test_ca_ca_index = *opt_ca_index_ptr.value();
 
     double cai_contrib[3] = {200., 0., 300.};
+    double test_ca_weight[3] = {0.25, 0., 1.};
+
     for (int i = 0; i<2; ++i) {
         test_ca_cai[i] = cai_contrib[test_ca_ca_index[i]];
     }
 
     std::vector<double> expected_iconc(3);
     for (int i = 0; i<3; ++i) {
-        expected_iconc[i] = math::lerp(cai_contrib[i], con_int, ion_iconc_weights[i]);
+        expected_iconc[i] = test_ca_weight[i]*cai_contrib[i] + ion_init_iconc[i];
     }
 
     ion.init_concentration();
@@ -776,7 +781,7 @@ TEST(fvm_lowered, gj_coords_simple) {
     d.add_gap_junction({1, 1});
     cells.push_back(std::move(d));
 
-    fvm_discretization D = fvm_discretize(cells);
+    fvm_discretization D = fvm_discretize(cells, neuron_parameter_defaults);
 
     std::vector<cell_gid_type> gids = {0, 1};
     auto GJ = fvcell.fvm_gap_junctions(cells, gids, rec, D);
@@ -886,7 +891,7 @@ TEST(fvm_lowered, gj_coords_complex) {
     std::vector<cell_gid_type> gids = {0, 1, 2};
 
     fvcell.fvm_intdom(rec, gids, cell_to_intdom);
-    fvm_discretization D = fvm_discretize(cells);
+    fvm_discretization D = fvm_discretize(cells, neuron_parameter_defaults);
 
     auto GJ = fvcell.fvm_gap_junctions(cells, gids, rec, D);
     EXPECT_EQ(10u, GJ.size());
@@ -971,8 +976,8 @@ TEST(fvm_lowered, cell_group_gj) {
     auto num_dom0 = fvcell.fvm_intdom(rec, gids_cg0, cell_to_intdom0);
     auto num_dom1 = fvcell.fvm_intdom(rec, gids_cg1, cell_to_intdom1);
 
-    fvm_discretization D0 = fvm_discretize(cell_group0);
-    fvm_discretization D1 = fvm_discretize(cell_group1);
+    fvm_discretization D0 = fvm_discretize(cell_group0, neuron_parameter_defaults);
+    fvm_discretization D1 = fvm_discretize(cell_group1, neuron_parameter_defaults);
 
     auto GJ0 = fvcell.fvm_gap_junctions(cell_group0, gids_cg0, rec, D0);
     auto GJ1 = fvcell.fvm_gap_junctions(cell_group1, gids_cg1, rec, D1);
diff --git a/test/unit/test_maputil.cpp b/test/unit/test_maputil.cpp
index 96ebd6d0724f554fd637598464df46130235ce9d..4cb6ed06d547de60005bbf56d41715ea2d748330 100644
--- a/test/unit/test_maputil.cpp
+++ b/test/unit/test_maputil.cpp
@@ -188,4 +188,18 @@ TEST(maputil, binary_search_index) {
         ASSERT_TRUE(opti);
         EXPECT_EQ(x, ns[*opti]);
     }
+
+    // With projection:
+    const char* ss[] = {"bit", "short", "small", "longer", "very long", "also long", "sesquipedalian"};
+    auto proj = [](const char* s) -> int { return std::strlen(s); };
+
+    for (int x: {1, 4, 7, 10, 20}) {
+        EXPECT_FALSE(binary_search_index(ss, x, proj));
+    }
+
+    for (int x: {3, 5, 6, 9, 14}) {
+        auto opti = binary_search_index(ss, x, proj);
+        ASSERT_TRUE(opti);
+        EXPECT_EQ(x, (int)std::strlen(ss[*opti]));
+    }
 }
diff --git a/test/unit/test_mc_cell_group.cpp b/test/unit/test_mc_cell_group.cpp
index ed9cc54345e8ed05a7878877152ebd0268894c36..5a453dd6d6056979e14c311e97222f05c26ca330 100644
--- a/test/unit/test_mc_cell_group.cpp
+++ b/test/unit/test_mc_cell_group.cpp
@@ -42,7 +42,12 @@ TEST(mc_cell_group, get_kind) {
 }
 
 TEST(mc_cell_group, test) {
-    mc_cell_group group{{0}, cable1d_recipe(make_cell()), lowered_cell()};
+    auto rec = cable1d_recipe(make_cell());
+    rec.nernst_ion("na");
+    rec.nernst_ion("ca");
+    rec.nernst_ion("k");
+
+    mc_cell_group group{{0}, rec, lowered_cell()};
     group.advance(epoch(0, 50), 0.01, {});
 
     // Model is expected to generate 4 spikes as a result of the
@@ -65,7 +70,12 @@ TEST(mc_cell_group, sources) {
     }
 
     std::vector<cell_gid_type> gids = {3u, 4u, 10u, 16u, 17u, 18u};
-    mc_cell_group group{gids, cable1d_recipe(cells), lowered_cell()};
+    auto rec = cable1d_recipe(cells);
+    rec.nernst_ion("na");
+    rec.nernst_ion("ca");
+    rec.nernst_ion("k");
+
+    mc_cell_group group{gids, rec, lowered_cell()};
 
     // Expect group sources to be lexicographically sorted by source id
     // with gids in cell group's range and indices starting from zero.
diff --git a/test/unit/test_mech_temperature.cpp b/test/unit/test_mech_temperature.cpp
index 0fae8d43deea58d324a5b0807f958545175f6be1..1a616c76e6699bb4769ca224924a1ad79119acda 100644
--- a/test/unit/test_mech_temperature.cpp
+++ b/test/unit/test_mech_temperature.cpp
@@ -28,8 +28,14 @@ void run_celsius_test() {
     auto instance = cat.instance<backend>("celsius_test");
     auto& celsius_test = instance.mech;
 
+    double temperature_K = 300.;
+    double temperature_C = temperature_K-273.15;
+
+    std::vector<fvm_value_type> temp(ncv, temperature_K);
+    std::vector<fvm_value_type> vinit(ncv, -65);
+
     auto shared_state = std::make_unique<typename backend::shared_state>(
-        ncell, cv_to_intdom, gj, celsius_test->data_alignment());
+        ncell, cv_to_intdom, gj, vinit, temp, celsius_test->data_alignment());
 
     mechanism_layout layout;
     mechanism_overrides overrides;
@@ -40,11 +46,7 @@ void run_celsius_test() {
     }
 
     celsius_test->instantiate(0, *shared_state, overrides, layout);
-
-    double temperature_K = 300.;
-    double temperature_C = temperature_K-273.15;
-
-    shared_state->reset(-65., temperature_K);
+    shared_state->reset();
 
     // expect 0 value in state 'c' after init:
 
@@ -59,19 +61,6 @@ void run_celsius_test() {
     expected_c_values.assign(ncv, temperature_C);
 
     EXPECT_EQ(expected_c_values, mechanism_field(celsius_test.get(), "c"));
-
-    // reset with new temperature and repeat test:
-
-    temperature_K = 290.;
-    temperature_C = temperature_K-273.15;
-
-    shared_state->reset(-65., temperature_K);
-    celsius_test->initialize();
-
-    celsius_test->nrn_state();
-    expected_c_values.assign(ncv, temperature_C);
-
-    EXPECT_EQ(expected_c_values, mechanism_field(celsius_test.get(), "c"));
 }
 
 TEST(mech_temperature, celsius) {
diff --git a/test/unit/test_synapses.cpp b/test/unit/test_synapses.cpp
index 0efc50f57f0838573a34e0f40230749326f62dcd..56700e8352fb9311bb6f8a740ba03ab678cda8c3 100644
--- a/test/unit/test_synapses.cpp
+++ b/test/unit/test_synapses.cpp
@@ -72,13 +72,15 @@ auto unique_cast(std::unique_ptr<B> p) {
 
 TEST(synapses, syn_basic_state) {
     using util::fill;
-    using value_type = multicore::backend::value_type;
-    using index_type = multicore::backend::index_type;
+    using value_type = fvm_value_type;
+    using index_type = fvm_index_type;
 
     int num_syn = 4;
     int num_comp = 4;
     int num_intdom = 1;
 
+    value_type temp_K = *neuron_parameter_defaults.temperature_K;
+
     auto expsyn = unique_cast<multicore::mechanism>(global_default_catalogue().instance<backend>("expsyn").mech);
     ASSERT_TRUE(expsyn);
 
@@ -87,9 +89,15 @@ TEST(synapses, syn_basic_state) {
 
     std::vector<fvm_gap_junction> gj = {};
     auto align = std::max(expsyn->data_alignment(), exp2syn->data_alignment());
-    shared_state state(num_intdom, std::vector<index_type>(num_comp, 0), gj, align);
 
-    state.reset(-65., constant::hh_squid_temp);
+    shared_state state(num_intdom,
+        std::vector<index_type>(num_comp, 0),
+        {},
+        std::vector<value_type>(num_comp, -65),
+        std::vector<value_type>(num_comp, temp_K),
+        align);
+
+    state.reset();
     fill(state.current_density, 1.0);
     fill(state.time_to, 0.1);
     state.set_dt();
diff --git a/test/unit/unit_test_catalogue.cpp b/test/unit/unit_test_catalogue.cpp
index b12af8da2580092de82048956c9c750f4eed6266..fc4987662ab6ead860b7af4044fac0072851613d 100644
--- a/test/unit/unit_test_catalogue.cpp
+++ b/test/unit/unit_test_catalogue.cpp
@@ -13,6 +13,9 @@
 #include "mechanisms/linear_ca_conc.hpp"
 #include "mechanisms/test_cl_valence.hpp"
 #include "mechanisms/test_ca_read_valence.hpp"
+#include "mechanisms/read_eX.hpp"
+#include "mechanisms/write_multiple_eX.hpp"
+#include "mechanisms/write_eX.hpp"
 
 #include "../gtest.h"
 
@@ -38,6 +41,9 @@ mechanism_catalogue make_unit_test_catalogue() {
     ADD_MECH(cat, linear_ca_conc)
     ADD_MECH(cat, test_cl_valence)
     ADD_MECH(cat, test_ca_read_valence)
+    ADD_MECH(cat, read_eX)
+    ADD_MECH(cat, write_multiple_eX)
+    ADD_MECH(cat, write_eX)
 
     return cat;
 }