diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6ff82e3a4657bd1d4889842a759e7c3597f38a44..b4fc22b8ac4eb769bb83077efdd4b9a80bc8717e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -218,6 +218,13 @@ else()
     set(ARB_WITH_EXTERNAL_MODCC FALSE)
 endif()
 
+set(ARB_MODCC_FLAGS)
+if(ARB_VECTORIZE)
+    list(APPEND ARB_MODCC_FLAGS "--simd")
+endif()
+if(ARB_WITH_PROFILING)
+    list(APPEND ARB_MODCC_FLAGS "--profile")
+endif()
 
 #----------------------------------------------------------
 # Configure targets in sub-directories.
diff --git a/arbor/backends/gpu/mechanism.cpp b/arbor/backends/gpu/mechanism.cpp
index e545e791806f8bef9e21a8d7c48e2fcf8a962d87..9a70b562210256d012b278179af85aa6ff71f822 100644
--- a/arbor/backends/gpu/mechanism.cpp
+++ b/arbor/backends/gpu/mechanism.cpp
@@ -76,6 +76,8 @@ void mechanism::instantiate(unsigned id,
     pp->vec_v_    = shared.voltage.data();
     pp->vec_i_    = shared.current_density.data();
 
+    pp->temperature_degC_ = shared.temperature_degC.data();
+
     auto ion_state_tbl = ion_state_table();
     num_ions_ = ion_state_tbl.size();
 
diff --git a/arbor/backends/gpu/mechanism_ppack_base.hpp b/arbor/backends/gpu/mechanism_ppack_base.hpp
index a867f69da375e985d55eda099d8801df56cb5adc..fb256579dbacd3325094a014c98c1e85f754d643 100644
--- a/arbor/backends/gpu/mechanism_ppack_base.hpp
+++ b/arbor/backends/gpu/mechanism_ppack_base.hpp
@@ -35,6 +35,7 @@ struct mechanism_ppack_base {
     const value_type* vec_dt_;
     const value_type* vec_v_;
     value_type* vec_i_;
+    const value_type* temperature_degC_;
 
     const index_type* node_index_;
     const value_type* weight_;
diff --git a/arbor/backends/gpu/shared_state.cpp b/arbor/backends/gpu/shared_state.cpp
index 9ab086088dbe6a973ab44602521209b17ad8cc3c..5cd8972c45ab44913977612d2ae7710e846c1300 100644
--- a/arbor/backends/gpu/shared_state.cpp
+++ b/arbor/backends/gpu/shared_state.cpp
@@ -120,6 +120,7 @@ shared_state::shared_state(
     dt_cv(n_cv),
     voltage(n_cv),
     current_density(n_cv),
+    temperature_degC(1),
     deliverable_events(n_cell)
 {}
 
@@ -139,6 +140,7 @@ void shared_state::reset(fvm_value_type initial_voltage, fvm_value_type temperat
     memory::fill(current_density, 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);
diff --git a/arbor/backends/gpu/shared_state.hpp b/arbor/backends/gpu/shared_state.hpp
index da1ae4effe82c7969fc548960d1b7aeb3377b82d..81174715d60c62e074da9db07cf4a507e2e07245 100644
--- a/arbor/backends/gpu/shared_state.hpp
+++ b/arbor/backends/gpu/shared_state.hpp
@@ -75,6 +75,7 @@ struct shared_state {
     array  dt_cv;             // Maps CV index to dt [ms].
     array  voltage;           // Maps CV index to membrane voltage [mV].
     array  current_density;   // Maps CV index to current density [A/m²].
+    array  temperature_degC;  // Global temperature [°C] (length 1 array).
 
     std::unordered_map<ionKind, ion_state> ion_data;
 
diff --git a/arbor/backends/multicore/mechanism.cpp b/arbor/backends/multicore/mechanism.cpp
index 81c48e8f890c1ec92d2ac7f41d57e8a728267e5c..63bd34493edb51f690e34ce1b53d6d294c96783b 100644
--- a/arbor/backends/multicore/mechanism.cpp
+++ b/arbor/backends/multicore/mechanism.cpp
@@ -78,6 +78,8 @@ void mechanism::instantiate(unsigned id, backend::shared_state& shared, const la
     vec_v_    = shared.voltage.data();
     vec_i_    = shared.current_density.data();
 
+    temperature_degC_ = &shared.temperature_degC;
+
     auto ion_state_tbl = ion_state_table();
     n_ion_ = ion_state_tbl.size();
     for (auto i: ion_state_tbl) {
diff --git a/arbor/backends/multicore/mechanism.hpp b/arbor/backends/multicore/mechanism.hpp
index 8724d8aa03704f1ebea7f506a8659b9eb17c86c7..af08a9318ee8af6e1d6f650d0c12bab3833b89f8 100644
--- a/arbor/backends/multicore/mechanism.hpp
+++ b/arbor/backends/multicore/mechanism.hpp
@@ -72,12 +72,13 @@ protected:
 
     // Non-owning views onto shared cell state, excepting ion state.
 
-    const index_type* vec_ci_;     // CV to cell index.
+    const index_type* vec_ci_;    // CV to cell index.
     const value_type* vec_t_;     // Cell index to cell-local time.
     const value_type* vec_t_to_;  // Cell index to cell-local integration step time end.
     const value_type* vec_dt_;    // CV to integration time step.
     const value_type* vec_v_;     // CV to cell membrane voltage.
     value_type* vec_i_;           // CV to cell membrane current density.
+    const value_type* temperature_degC_; // Pointer to global temperature scalar.
     deliverable_event_stream* event_stream_ptr_;
 
     // Per-mechanism index and weight data, excepting ion indices.
diff --git a/arbor/backends/multicore/shared_state.cpp b/arbor/backends/multicore/shared_state.cpp
index 6cb520ef3e0cd4843bd2eb23f92a350a1e5c3631..38120f4a15fecb7725943d5a286b304669c954c9 100644
--- a/arbor/backends/multicore/shared_state.cpp
+++ b/arbor/backends/multicore/shared_state.cpp
@@ -1,3 +1,4 @@
+#include <cfloat>
 #include <cmath>
 #include <iostream>
 #include <string>
@@ -129,6 +130,7 @@ shared_state::shared_state(
     dt_cv(n_cv, pad(alignment)),
     voltage(n_cv, pad(alignment)),
     current_density(n_cv, pad(alignment)),
+    temperature_degC(NAN),
     deliverable_events(n_cell)
 {
     // For indices in the padded tail of cv_to_cell, set index to last valid cell index.
@@ -155,6 +157,7 @@ void shared_state::reset(fvm_value_type initial_voltage, fvm_value_type temperat
     util::fill(current_density, 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);
diff --git a/arbor/backends/multicore/shared_state.hpp b/arbor/backends/multicore/shared_state.hpp
index 294f453430373fbbec62be5dd0e5a8e2798679a0..25247f1435d377eb8a01a107738ca49d656f0907 100644
--- a/arbor/backends/multicore/shared_state.hpp
+++ b/arbor/backends/multicore/shared_state.hpp
@@ -94,6 +94,7 @@ struct shared_state {
     array  dt_cv;             // Maps CV index to dt [ms].
     array  voltage;           // Maps CV index to membrane voltage [mV].
     array  current_density;   // Maps CV index to current density [A/m²].
+    fvm_value_type temperature_degC;  // Global temperature [°C].
 
     std::unordered_map<ionKind, ion_state> ion_data;
 
diff --git a/aux/CMakeLists.txt b/aux/CMakeLists.txt
index d30301542bc64f71178b77dbc3ac525e18f9ecf3..2a23eb46bfbdc36d13659ad45d97bb435201854e 100644
--- a/aux/CMakeLists.txt
+++ b/aux/CMakeLists.txt
@@ -8,6 +8,7 @@ set(aux-sources
 )
 
 add_library(arbor-aux ${aux-sources})
+target_compile_options(arbor-aux PRIVATE ${ARB_CXXOPT_ARCH})
 target_link_libraries(arbor-aux PUBLIC ext-json arbor)
 target_include_directories(arbor-aux PUBLIC include)
 set_target_properties(arbor-aux PROPERTIES OUTPUT_NAME arboraux)
diff --git a/include/arbor/distributed_context.hpp b/include/arbor/distributed_context.hpp
index 5f50baa99397d055e70b0bb287c6f1ae77fad8ef..4c95292ab127851e4a4420efa3de355d4ba66e9c 100644
--- a/include/arbor/distributed_context.hpp
+++ b/include/arbor/distributed_context.hpp
@@ -88,7 +88,7 @@ private:
         virtual void barrier() const = 0;
         virtual std::string name() const = 0;
 
-        ARB_PP_FOREACH(ARB_INTERFACE_COLLECTIVES_, ARB_COLLECTIVE_TYPES_);
+        ARB_PP_FOREACH(ARB_INTERFACE_COLLECTIVES_, ARB_COLLECTIVE_TYPES_)
         virtual std::vector<std::string> gather(std::string value, int root) const = 0;
 
         virtual ~interface() {}
diff --git a/mechanisms/BuildModules.cmake b/mechanisms/BuildModules.cmake
index a10850f66eed3fbe72afc797942b52d53d459add..945671f21d565ad2ca487979f56d74d9baf95915 100644
--- a/mechanisms/BuildModules.cmake
+++ b/mechanisms/BuildModules.cmake
@@ -5,6 +5,15 @@ include(CMakeParseArguments)
 function(build_modules)
     cmake_parse_arguments(build_modules "" "MODCC;TARGET;SOURCE_DIR;DEST_DIR;MECH_SUFFIX" "MODCC_FLAGS;GENERATES" ${ARGN})
 
+    if("${build_modules_SOURCE_DIR}" STREQUAL "")
+        set(build_modules_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+    endif()
+
+    if("${build_modules_DEST_DIR}" STREQUAL "")
+        set(build_modules_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+    endif()
+    file(MAKE_DIRECTORY "${build_modules_DEST_DIR}")
+
     set(all_generated)
     foreach(mech ${build_modules_UNPARSED_ARGUMENTS})
         set(mod "${build_modules_SOURCE_DIR}/${mech}.mod")
diff --git a/mechanisms/CMakeLists.txt b/mechanisms/CMakeLists.txt
index 2ad38b1126ea4025fee0d9a4685b2c937c916c1b..3597672e3af91f650dbaaa468a40317c735fe023 100644
--- a/mechanisms/CMakeLists.txt
+++ b/mechanisms/CMakeLists.txt
@@ -10,14 +10,6 @@ set(mod_srcdir "${CMAKE_CURRENT_SOURCE_DIR}/mod")
 set(mech_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
 file(MAKE_DIRECTORY "${mech_dir}")
 
-set(modcc_flags)
-if(ARB_VECTORIZE)
-    list(APPEND modcc_flags "--simd")
-endif()
-if(ARB_WITH_PROFILING)
-    list(APPEND modcc_flags "--profile")
-endif()
-
 set(external_modcc)
 if(ARB_WITH_EXTERNAL_MODCC)
     set(external_modcc MODCC ${modcc})
@@ -28,7 +20,7 @@ build_modules(
     SOURCE_DIR "${mod_srcdir}"
     DEST_DIR "${mech_dir}"
     ${external_modcc}
-    MODCC_FLAGS -t cpu -t gpu ${modcc_flags}
+    MODCC_FLAGS -t cpu -t gpu ${ARB_MODCC_FLAGS}
     GENERATES .hpp _cpu.cpp _gpu.cpp _gpu.cu
     TARGET build_all_mods
 )
diff --git a/mechanisms/mod/hh.mod b/mechanisms/mod/hh.mod
index 00554aecfba7a35679c90b60c255ae2ee14e5b6f..3e7a145ced520677f472632d8a773e6b6ac6e730 100644
--- a/mechanisms/mod/hh.mod
+++ b/mechanisms/mod/hh.mod
@@ -17,7 +17,7 @@ PARAMETER {
     gkbar = .036 (S/cm2)
     gl = .0003 (S/cm2)
     el = -54.3 (mV)
-    celsius = 6.3 (degC)
+    celsius
 }
 
 STATE {
@@ -47,20 +47,20 @@ BREAKPOINT {
 }
 
 INITIAL {
-    rates(v)
+    rates(v, celsius)
     m = minf
     h = hinf
     n = ninf
 }
 
 DERIVATIVE states {
-    rates(v)
+    rates(v, celsius)
     m' = (minf-m)/mtau
     h' = (hinf-h)/htau
     n' = (ninf-n)/ntau
 }
 
-PROCEDURE rates(v)
+PROCEDURE rates(v, celsius)
 {
     LOCAL  alpha, beta, sum, q10
 
diff --git a/mechanisms/mod/kamt.mod b/mechanisms/mod/kamt.mod
index d897163b24634d650b969ad28a3665038de7fec3..e218e8b3f8ec2a1facabbb76b56cdb130467121b 100644
--- a/mechanisms/mod/kamt.mod
+++ b/mechanisms/mod/kamt.mod
@@ -59,18 +59,18 @@ BREAKPOINT {
 }
 
 INITIAL {
-    trates(v)
+    trates(v,celsius)
     m=minf
     h=hinf
 }
 
 DERIVATIVE states {
-    trates(v)
+    trates(v,celsius)
     m' = (minf-m)/mtau
     h' = (hinf-h)/htau
 }
 
-PROCEDURE trates(v) {
+PROCEDURE trates(v,celsius) {
     LOCAL qt
     qt=q10^((celsius-24)/10)
 
diff --git a/mechanisms/mod/kdrmt.mod b/mechanisms/mod/kdrmt.mod
index c61ea5e92ff04265f3bc4631189fdfb30fd8b857..54c070441deb82925b7468c0e04d62de9fe4a205 100644
--- a/mechanisms/mod/kdrmt.mod
+++ b/mechanisms/mod/kdrmt.mod
@@ -50,16 +50,16 @@ BREAKPOINT {
 }
 
 INITIAL {
-    trates(v)
+    trates(v,celsius)
     m=minf
 }
 
 DERIVATIVE states {
-    trates(v)
+    trates(v,celsius)
     m' = (minf-m)/mtau
 }
 
-PROCEDURE trates(v) {
+PROCEDURE trates(v,celsius) {
     LOCAL qt
     LOCAL alpm, betm
     LOCAL tmp
diff --git a/mechanisms/mod/nax.mod b/mechanisms/mod/nax.mod
index 2feddda26aca3a6c3f01a06855cec3e11df448c2..931fe82fb08d7f4bee717e9d49290f5c2299bc87 100644
--- a/mechanisms/mod/nax.mod
+++ b/mechanisms/mod/nax.mod
@@ -63,18 +63,18 @@ BREAKPOINT {
 }
 
 INITIAL {
-    trates(v,sh)
+    trates(v,sh,celsius)
     m=minf
     h=hinf
 }
 
 DERIVATIVE states {
-    trates(v,sh)
+    trates(v,sh,celsius)
     m' = (minf-m)/mtau
     h' = (hinf-h)/htau
 }
 
-PROCEDURE trates(vm,sh2) {
+PROCEDURE trates(vm,sh2,celsius) {
     LOCAL  a, b, qt
     qt=q10^((celsius-24)/10)
     a = trap0(vm,tha+sh2,Ra,qa)
diff --git a/modcc/expression.cpp b/modcc/expression.cpp
index 5689fe5599ad82c2855bf888d2c1d52755fff142..960339f80043842f6643de94c6e563e5679047cf 100644
--- a/modcc/expression.cpp
+++ b/modcc/expression.cpp
@@ -92,14 +92,27 @@ void IdentifierExpression::semantic(scope_ptr scp) {
                        yellow(spelling_)));
         return;
     }
-    // if the symbol is an indexed variable, this is the first time that the
-    // indexed variable is used in this procedure. In which case, we create
-    // a local variable which refers to the indexed variable, which will be
-    // found for any subsequent variable lookup inside the procedure
+
+    // If the symbol is an indexed variable, and we're in an API block,
+    // create a local variable which refers to the indexed variable,
+    // which will be found for any subsequent variable lookup inside the
+    // procedure.
+    //
+    // If, however, we are in a PROCEDURE or FUNCTION block, we do not
+    // have access to indexed variables and this constitutes an error.
+
     if(auto sym = s->is_indexed_variable()) {
-        auto var = new LocalVariable(location_, spelling_);
-        var->external_variable(sym);
-        s = scope_->add_local_symbol(spelling_, scope_type::symbol_ptr{var});
+        if (scope_->in_api_context()) {
+            auto var = new LocalVariable(location_, spelling_);
+            var->external_variable(sym);
+            s = scope_->add_local_symbol(spelling_, scope_type::symbol_ptr{var});
+        }
+        else {
+            error( pprintf("the symbol '%' refers to an external quantity "
+                           "and is unavailable in a function or procedure",
+                           yellow(spelling_)));
+            return;
+        }
     }
 
     // save the symbol
@@ -445,9 +458,11 @@ std::string ProcedureExpression::to_string() const {
     return str;
 }
 
-void ProcedureExpression::semantic(scope_type::symbol_map &global_symbols) {
+void ProcedureExpression::semantic(scope_ptr scp) {
+    scope_ = scp;
+
     // assert that the symbol is already visible in the global_symbols
-    if(global_symbols.find(name()) == global_symbols.end()) {
+    if(scope_->find_global(name()) == nullptr) {
         throw compiler_exception(
             "attempt to perform semantic analysis for procedure '"
             + yellow(name())
@@ -455,9 +470,6 @@ void ProcedureExpression::semantic(scope_type::symbol_map &global_symbols) {
             location_);
     }
 
-    // create the scope for this procedure
-    scope_ = std::make_shared<scope_type>(global_symbols);
-
     // add the argumemts to the list of local variables
     for(auto& a : args_) {
         a->semantic(scope_);
@@ -476,6 +488,23 @@ void ProcedureExpression::semantic(scope_type::symbol_map &global_symbols) {
     symbol_ = scope_->find_global(name());
 }
 
+void ProcedureExpression::semantic(scope_type::symbol_map &global_symbols) {
+    // create the scope for this procedure and run semantic pass on it
+    scope_ptr scp = std::make_shared<scope_type>(global_symbols);
+    switch (kind_) {
+    case procedureKind::derivative:
+    case procedureKind::kinetic:
+    case procedureKind::initial:
+    case procedureKind::breakpoint:
+        scp->in_api_context(true);
+        break;
+    default:
+        scp->in_api_context(false);
+        break;
+    }
+    semantic(scp);
+}
+
 /*******************************************************************************
   APIMethod
 *******************************************************************************/
@@ -500,6 +529,14 @@ std::string APIMethod::to_string() const {
     return str;
 }
 
+void APIMethod::semantic(scope_type::symbol_map &global_symbols) {
+    // create the scope for this procedure, marking it as an API context,
+    // and run semantic pass on it
+    scope_ptr scp = std::make_shared<scope_type>(global_symbols);
+    scp->in_api_context(true);
+    semantic(scp);
+}
+
 /*******************************************************************************
   InitialBlock
 *******************************************************************************/
diff --git a/modcc/expression.hpp b/modcc/expression.hpp
index 1b793591a3e73f099a2314953d5dcc686ecb7ca9..12b9fc5f174aede12fbe6c67f59c1f235db044c7 100644
--- a/modcc/expression.hpp
+++ b/modcc/expression.hpp
@@ -1021,6 +1021,7 @@ public:
         body_ = std::move(new_body);
     }
 
+    void semantic(scope_ptr scp) override;
     void semantic(scope_type::symbol_map &scp) override;
     ProcedureExpression* is_procedure() override {return this;}
     std::string to_string() const override;
@@ -1049,6 +1050,8 @@ public:
         :   ProcedureExpression(loc, std::move(name), std::move(args), std::move(body), procedureKind::api)
     {}
 
+    using ProcedureExpression::semantic;
+    void semantic(scope_type::symbol_map &scp) override;
     APIMethod* is_api_method() override {return this;}
     void accept(Visitor *v) override;
 
diff --git a/modcc/identifier.hpp b/modcc/identifier.hpp
index 6642caacf668b12f17981ac2b05be0509e1b480c..3ddd3c56ea34dcd853b0d3a6d0d1984be3c509a8 100644
--- a/modcc/identifier.hpp
+++ b/modcc/identifier.hpp
@@ -51,6 +51,7 @@ enum class sourceKind {
     ion_revpot,
     ion_iconc,
     ion_econc,
+    temperature,
     no_source
 };
 
diff --git a/modcc/module.cpp b/modcc/module.cpp
index dbabd32989770f0bd0e7d4bcd4c4df7ef6182be5..81312784081f2bf80f15b78d191eb8b8db502966 100644
--- a/modcc/module.cpp
+++ b/modcc/module.cpp
@@ -4,6 +4,7 @@
 #include <iostream>
 #include <memory>
 #include <set>
+#include <string>
 #include <unordered_set>
 
 #include "errorvisitor.hpp"
@@ -467,17 +468,32 @@ void Module::add_variables_to_symbols() {
             continue;
         }
 
-        // Parameters are scalar by default, but may later be changed to range.
-        linkageKind linkage = linkageKind::local;
-        auto& sym = create_variable(id.token,
-            accessKind::read, visibilityKind::global, linkage, rangeKind::scalar);
+        // Special case: 'celsius' is an external indexed-variable with a special
+        // data source. Retrieval of value is handled especially by printers.
 
-        // set default value if one was specified
-        if (id.has_value()) {
-            sym->is_variable()->value(std::stod(id.value));
+        if (id.name() == "celsius") {
+            create_indexed_variable("celsius", "celsius",
+                sourceKind::temperature, tok::eq, accessKind::read, ionKind::none, Location());
+        }
+        else {
+            // Parameters are scalar by default, but may later be changed to range.
+            auto& sym = create_variable(id.token,
+                accessKind::read, visibilityKind::global, linkageKind::local, rangeKind::scalar);
+
+            // Set default value if one was specified.
+            if (id.has_value()) {
+                sym->is_variable()->value(std::stod(id.value));
+            }
         }
     }
 
+    // Remove `celsius` from the parameter block, as it is not a true parameter anymore.
+    parameter_block_.parameters.erase(
+        std::remove_if(parameter_block_.begin(), parameter_block_.end(),
+            [](const Id& id) { return id.name() == "celsius"; }),
+        parameter_block_.end()
+    );
+
     // Add 'assigned' variables, ignoring built-in voltage variable "v".
     for (const Id& id: assigned_block_) {
         if (id.name() == "v") {
diff --git a/modcc/printer/cprinter.cpp b/modcc/printer/cprinter.cpp
index 0c7a6376ffd031ec1214f84801e0b7c3f61046b5..99cc3dc9d09367b238234250134613aad691b60d 100644
--- a/modcc/printer/cprinter.cpp
+++ b/modcc/printer/cprinter.cpp
@@ -394,7 +394,12 @@ void CPrinter::visit(VariableExpression *sym) {
 
 void CPrinter::visit(IndexedVariable *sym) {
     indexed_variable_info v = decode_indexed_variable(sym);
-    out_ << v.data_var << "[" << v.index_var << "[i_]]";
+    if (v.scalar()) {
+        out_ << v.data_var << "[0]";
+    }
+    else {
+        out_ << v.data_var << "[" << v.index_var << "[i_]]";
+    }
 }
 
 void CPrinter::visit(CallExpression* e) {
@@ -450,6 +455,10 @@ void emit_state_read(std::ostream& out, LocalVariable* local) {
 void emit_state_update(std::ostream& out, Symbol* from, IndexedVariable* external) {
     if (!external->is_write()) return;
 
+    if (decode_indexed_variable(external).scalar()) {
+        throw compiler_exception("Cannot assign to global scalar: "+external->to_string());
+    }
+
     const char* op = external->op()==tok::plus? " += ": " -= ";
     out << cprint(external) << op << from->name() << ";\n";
 }
@@ -524,9 +533,14 @@ void SimdPrinter::visit(AssignmentExpression* e) {
 
 void SimdPrinter::visit(IndexedVariable *sym) {
     indexed_variable_info v = decode_indexed_variable(sym);
-    out_ << "S::indirect(" << v.data_var
-         << ", " << index_i_name(v.index_var)
-         << ", constraint_category_)";
+    if (v.scalar()) {
+        out_ << v.data_var << "[0]";
+    }
+    else {
+        out_ << "S::indirect(" << v.data_var
+             << ", " << index_i_name(v.index_var)
+             << ", constraint_category_)";
+    }
 }
 
 void SimdPrinter::visit(CallExpression* e) {
@@ -576,18 +590,23 @@ void emit_simd_state_read(std::ostream& out, LocalVariable* local, simd_expr_con
 
     if (local->is_read()) {
         indexed_variable_info v = decode_indexed_variable(local->external_variable());
-        if(constraint == simd_expr_constraint::contiguous) {
+        if (v.scalar()) {
+            out << "(" << v.data_var
+                << "[0]);\n";
+        }
+        else if (constraint == simd_expr_constraint::contiguous) {
             out << "(" <<  v.data_var
-                 << " + " << v.index_var
-                 << "[index_]);\n";
+                << " + " << v.index_var
+                << "[index_]);\n";
         }
-        else if(constraint == simd_expr_constraint::constant){
+        else if (constraint == simd_expr_constraint::constant) {
             out << "(" << v.data_var
-                 << "[" << v.index_var
-                 << "element0]);\n";
+                << "[" << v.index_var
+                << "element0]);\n";
         }
-        else
+        else {
             out << "(" <<  simdprint(local->external_variable()) << ");\n";
+        }
     }
     else {
         out << " = 0;\n";
@@ -600,14 +619,19 @@ void emit_simd_state_update(std::ostream& out, Symbol* from, IndexedVariable* ex
     const char* op = external->op()==tok::plus? " += ": " -= ";
     indexed_variable_info v = decode_indexed_variable(external);
 
-    if(constraint == simd_expr_constraint::contiguous) {
-        out << "simd_value t_"<< external->name() <<"(" << v.data_var << " + " << v.index_var << "[index_]);\n";
-        out << "t_" << external->name() << op << from->name() << ";\n";
-        out << "t_" << external->name() << ".copy_to(" << v.data_var << " + " << v.index_var << "[index_]);\n";
-
+    if (v.scalar()) {
+        throw compiler_exception("Cannot assign to global scalar: "+external->to_string());
     }
     else {
-        out << simdprint(external) << op << from->name() << ";\n";
+        if (constraint == simd_expr_constraint::contiguous) {
+            out << "simd_value t_"<< external->name() <<"(" << v.data_var << " + " << v.index_var << "[index_]);\n";
+            out << "t_" << external->name() << op << from->name() << ";\n";
+            out << "t_" << external->name() << ".copy_to(" << v.data_var << " + " << v.index_var << "[index_]);\n";
+
+        }
+        else {
+            out << simdprint(external) << op << from->name() << ";\n";
+        }
     }
 }
 
@@ -674,9 +698,16 @@ void emit_simd_api_body(std::ostream& out, APIMethod* method, moduleKind module_
     auto body = method->body();
     auto indexed_vars = indexed_locals(method->scope());
 
+    std::vector<LocalVariable*> scalar_indexed_vars;
     std::unordered_set<std::string> indices;
     for (auto& sym: indexed_vars) {
-        indices.insert(decode_indexed_variable(sym->external_variable()).index_var);
+        auto info = decode_indexed_variable(sym->external_variable());
+        if (!info.scalar()) {
+            indices.insert(info.index_var);
+        }
+        else {
+            scalar_indexed_vars.push_back(sym);
+        }
     }
 
     if (!body->statements().empty()) {
@@ -718,11 +749,16 @@ void emit_simd_api_body(std::ostream& out, APIMethod* method, moduleKind module_
 
         }
         else {
-            out << "unsigned n_ = width_;\n\n";
+            // We may nonetheless need to read a global scalar indexed variable.
+            for (auto& sym: scalar_indexed_vars) {
+                emit_simd_state_read(out, sym, simd_expr_constraint::other);
+            }
+
             out <<
-                "for (unsigned i_ = 0; i_ < n_; i_ += simd_width_) {\n" << indent;
-            out << simdprint(body);
-            out << popindent << "}\n";
+                "unsigned n_ = width_;\n\n"
+                "for (unsigned i_ = 0; i_ < n_; i_ += simd_width_) {\n" << indent <<
+                simdprint(body) << popindent <<
+                "}\n";
         }
     }
 }
diff --git a/modcc/printer/cudaprinter.cpp b/modcc/printer/cudaprinter.cpp
index 86849396a4bc524919e226e2f2f518bf0f0692bb..1013d5a80cd6efc59d84133e672c677505283c61 100644
--- a/modcc/printer/cudaprinter.cpp
+++ b/modcc/printer/cudaprinter.cpp
@@ -372,7 +372,10 @@ void emit_api_body_cu(std::ostream& out, APIMethod* e, bool is_point_proc) {
 
     std::unordered_set<std::string> indices;
     for (auto& sym: indexed_vars) {
-        indices.insert(decode_indexed_variable(sym->external_variable()).index_var);
+        auto d = decode_indexed_variable(sym->external_variable());
+        if (!d.scalar()) {
+            indices.insert(d.index_var);
+        }
     }
 
     if (!body->statements().empty()) {
@@ -420,6 +423,10 @@ void emit_state_update_cu(std::ostream& out, Symbol* from,
     const bool is_minus = external->op()==tok::minus;
     auto d = decode_indexed_variable(external);
 
+    if (d.scalar()) {
+        throw compiler_exception("Cannot assign to global scalar: "+external->to_string());
+    }
+
     if (is_point_proc) {
         out << "arb::gpu::reduce_by_key(";
         is_minus && out << "-";
@@ -439,7 +446,12 @@ void CudaPrinter::visit(VariableExpression *sym) {
 
 void CudaPrinter::visit(IndexedVariable *e) {
     auto d = decode_indexed_variable(e);
-    out_ << "params_." << d.data_var << "[" << index_i_name(d.index_var) <<  "]";
+    if (d.scalar()) {
+        out_ << "params_." << d.data_var << "[0]";
+    }
+    else {
+        out_ << "params_." << d.data_var << "[" << index_i_name(d.index_var) <<  "]";
+    }
 }
 
 void CudaPrinter::visit(CallExpression* e) {
diff --git a/modcc/printer/printerutil.cpp b/modcc/printer/printerutil.cpp
index 6050fa06bf730bd497a63fa4f5f05bdc95da6de2..09bc4c7371384d6e21368aeb87af8f898d3d8567 100644
--- a/modcc/printer/printerutil.cpp
+++ b/modcc/printer/printerutil.cpp
@@ -144,6 +144,10 @@ indexed_variable_info decode_indexed_variable(IndexedVariable* sym) {
     case sourceKind::ion_econc:
         data_var=ion_pfx+".external_concentration";
         break;
+    case sourceKind::temperature:
+        data_var="temperature_degC_";
+        index_var=""; // scalar global
+        break;
     default:
         throw compiler_exception(pprintf("unrecognized indexed data source: %", sym), sym->location());
     }
diff --git a/modcc/printer/printerutil.hpp b/modcc/printer/printerutil.hpp
index 6881d2cd4a2da21764caa0065dbcfb29333e0ea7..208c577af369c07705909afcd880ec7e5517c31b 100644
--- a/modcc/printer/printerutil.hpp
+++ b/modcc/printer/printerutil.hpp
@@ -117,6 +117,7 @@ NetReceiveExpression* find_net_receive(const Module& m);
 struct indexed_variable_info {
     std::string data_var;
     std::string index_var;
+    bool scalar() const { return index_var.empty(); }
 };
 
 indexed_variable_info decode_indexed_variable(IndexedVariable* sym);
diff --git a/modcc/scope.hpp b/modcc/scope.hpp
index be2eee29450104c0e85d36e75004e9ee3bca2b92..d7aca81e4e0a952513d6c8162f1f5b9e1bfc253e 100644
--- a/modcc/scope.hpp
+++ b/modcc/scope.hpp
@@ -31,9 +31,18 @@ public:
     symbol_map& locals();
     symbol_map* globals();
 
+    bool in_api_context() const {
+        return api_context_;
+    }
+
+    void in_api_context(bool flag) {
+        api_context_ = flag;
+    }
+
 private:
     symbol_map* global_symbols_=nullptr;
     symbol_map  local_symbols_;
+    bool api_context_ = false;
 };
 
 template<typename Symbol>
diff --git a/modcc/solvers.hpp b/modcc/solvers.hpp
index e2bbc35323cda815a5f1918d9a16b7e00189d2b9..33f5b00bb50fe9b94e0ff17651151d5e6d145fbe 100644
--- a/modcc/solvers.hpp
+++ b/modcc/solvers.hpp
@@ -49,6 +49,9 @@ public:
                    " but no METHOD was specified in the SOLVE statement",
                    deriv->location()});
         }
+        else {
+            visit((Expression*)e);
+        }
     }
 };
 
diff --git a/test/ubench/CMakeLists.txt b/test/ubench/CMakeLists.txt
index a63aee730f4527a4275479037c436d35c81fa75d..a0fb0040738736606125f3016c110db7b47d8167 100644
--- a/test/ubench/CMakeLists.txt
+++ b/test/ubench/CMakeLists.txt
@@ -24,6 +24,7 @@ foreach(bench_src ${bench_sources})
     string(REGEX REPLACE "\\.[^.]*$" "" bench_exe ${bench_src})
     add_executable(${bench_exe} EXCLUDE_FROM_ALL "${bench_src}")
     target_link_libraries(${bench_exe} arbor arbor-private-headers ext-benchmark)
+    target_compile_options(${bench_exe} PRIVATE ${ARB_CXXOPT_ARCH})
     list(APPEND bench_exe_list ${bench_exe})
 endforeach()
 
diff --git a/test/unit-distributed/CMakeLists.txt b/test/unit-distributed/CMakeLists.txt
index 6e5030f05dd3eaa19f97d8c15519063bce4bfa88..fa7d15f98b71c891a9d6ab2f9daec87d4f849375 100644
--- a/test/unit-distributed/CMakeLists.txt
+++ b/test/unit-distributed/CMakeLists.txt
@@ -9,13 +9,13 @@ set(unit-distributed_sources
 )
 
 add_executable(unit-local ${unit-distributed_sources})
-target_compile_options(unit-local PRIVATE ${CXXOPT_ARCH})
+target_compile_options(unit-local PRIVATE ${ARB_CXXOPT_ARCH})
 target_compile_definitions(unit-local PRIVATE TEST_LOCAL)
 target_link_libraries(unit-local PRIVATE gtest arbor arbor-aux arbor-private-headers)
 
 if(ARB_WITH_MPI)
     add_executable(unit-mpi ${unit-distributed_sources})
-    target_compile_options(unit-mpi PRIVATE ${CXXOPT_ARCH})
+    target_compile_options(unit-mpi PRIVATE ${ARB_CXXOPT_ARCH})
     target_compile_definitions(unit-mpi PRIVATE TEST_MPI)
     target_link_libraries(unit-mpi PRIVATE gtest arbor arbor-aux arbor-private-headers)
 endif()
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 42205d7da6e082249b2a2bfb97e1a2c08b339170..2945330af8a482cdf0ebbb28d995d940fd684803 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -1,10 +1,40 @@
+# Build mechanisms used solely in unit tests.
+
+set(test_mechanisms
+    celsius_test
+)
+
+include(${PROJECT_SOURCE_DIR}/mechanisms/BuildModules.cmake)
+
+set(external_modcc)
+if(ARB_WITH_EXTERNAL_MODCC)
+    set(external_modcc MODCC ${modcc})
+endif()
+set(test_mech_dir ${CMAKE_CURRENT_BINARY_DIR}/mechanisms)
+
+build_modules(
+    ${test_mechanisms}
+    SOURCE_DIR mod
+    DEST_DIR "${test_mech_dir}"
+    ${external_modcc}
+    MODCC_FLAGS -t cpu -t gpu ${ARB_MODCC_FLAGS}
+    GENERATES .hpp _cpu.cpp _gpu.cpp _gpu.cu
+    TARGET build_test_mods
+)
+
+set(test_mech_sources)
+foreach(mech ${test_mechanisms})
+    list(APPEND test_mech_sources ${test_mech_dir}/${mech}_cpu.cpp)
+    if(ARB_WITH_CUDA)
+        list(APPEND test_mech_sources ${test_mech_dir}/${mech}_gpu.cpp)
+        list(APPEND test_mech_sources ${test_mech_dir}/${mech}_gpu.cu)
+    endif()
+endforeach()
+
+
 # TODO: test_mechanism and mechanism prototype comparisons must
 # be re-jigged.
 
-# Build prototype mechanisms for testing in test_mechanisms.
-#
-#include(${PROJECT_SOURCE_DIR}/mechanisms/BuildModules.cmake)
-#
 # set(proto_mechanisms pas hh expsyn exp2syn test_kin1 test_kinlva test_ca)
 # set(mech_proto_dir "${CMAKE_CURRENT_BINARY_DIR}/mech_proto")
 # file(MAKE_DIRECTORY "${mech_proto_dir}")
@@ -46,6 +76,7 @@ set(unit_sources
     test_matrix.cpp
     test_mc_cell.cpp
     test_mechanisms.cpp
+    test_mech_temperature.cpp
     test_mechcat.cpp
     test_merge_events.cpp
     test_multi_event_stream.cpp
@@ -83,7 +114,9 @@ set(unit_sources
     test.cpp
 
     # common routines
+    mech_private_field_access.cpp
     stats.cpp
+    unit_test_catalogue.cpp
 )
 
 if(ARB_WITH_CUDA)
@@ -103,8 +136,9 @@ if(ARB_WITH_CUDA)
     )
 endif()
 
-add_executable(unit ${unit_sources})
-target_compile_options(unit PRIVATE ${CXXOPT_ARCH})
+add_executable(unit ${unit_sources} ${test_mech_sources})
+add_dependencies(unit build_test_mods)
+target_compile_options(unit PRIVATE ${ARB_CXXOPT_ARCH})
 target_compile_definitions(unit PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/swc\"")
+target_include_directories(unit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
 target_link_libraries(unit PRIVATE gtest arbor arbor-private-headers arbor-aux)
-
diff --git a/test/unit/mech_private_field_access.cpp b/test/unit/mech_private_field_access.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3653e87b64546251cdde57128f249043c1b7e610
--- /dev/null
+++ b/test/unit/mech_private_field_access.cpp
@@ -0,0 +1,64 @@
+#include <arbor/version.hpp>
+
+#include "backends/multicore/fvm.hpp"
+#include "backends/multicore/mechanism.hpp"
+#include "util/maputil.hpp"
+
+#ifdef ARB_GPU_ENABLED
+#include "backends/gpu/fvm.hpp"
+#include "backends/gpu/mechanism.hpp"
+#include "memory/cuda_wrappers.hpp"
+#endif
+
+#include "common.hpp"
+#include "mech_private_field_access.hpp"
+
+using namespace arb;
+using field_table_type = std::vector<std::pair<const char*, fvm_value_type**>>;
+
+// Multicore mechanisms:
+
+ACCESS_BIND(field_table_type (multicore::mechanism::*)(), multicore_field_table_ptr, &multicore::mechanism::field_table)
+
+std::vector<fvm_value_type> mechanism_field(multicore::mechanism* m, const std::string& key) {
+    auto opt_ptr = util::value_by_key((m->*multicore_field_table_ptr)(), key);
+    if (!opt_ptr) throw std::logic_error("internal error: no such field in mechanism");
+
+    const fvm_value_type* field_data = *opt_ptr.value();
+    return std::vector<fvm_value_type>(field_data, field_data+m->size());
+}
+
+// GPU mechanisms:
+
+#ifdef ARB_GPU_ENABLED
+ACCESS_BIND(field_table_type (gpu::mechanism::*)(), gpu_field_table_ptr, &gpu::mechanism::field_table)
+
+std::vector<fvm_value_type> mechanism_field(gpu::mechanism* m, const std::string& key) {
+    auto opt_ptr = util::value_by_key((m->*gpu_field_table_ptr)(), key);
+    if (!opt_ptr) throw std::logic_error("internal error: no such field in mechanism");
+
+    const fvm_value_type* field_data = *opt_ptr.value();
+    std::vector<fvm_value_type> values(m->size());
+
+    cudaDeviceSynchronize();
+    memory::cuda_memcpy_d2h(values.data(), field_data, sizeof(fvm_value_type)*m->size());
+    return values;
+}
+#endif
+
+// Generic access:
+
+std::vector<fvm_value_type> mechanism_field(mechanism* m, const std::string& key) {
+    if (auto p = dynamic_cast<multicore::mechanism*>(m)) {
+        return mechanism_field(p, key);
+    }
+
+#ifdef ARB_GPU_ENABLED
+    if (auto p = dynamic_cast<gpu::mechanism*>(m)) {
+        return mechanism_field(p, key);
+    }
+#endif
+
+    throw std::logic_error("internal error: mechanism instantiated on unknown backend");
+}
+
diff --git a/test/unit/mech_private_field_access.hpp b/test/unit/mech_private_field_access.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..50c7b26bc8a54190920ecb2b963af94ed9eea4f7
--- /dev/null
+++ b/test/unit/mech_private_field_access.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <arbor/mechanism.hpp>
+
+// Get a copy of the data within a mechanisms's (private) named field.
+
+std::vector<arb::fvm_value_type> mechanism_field(arb::mechanism* m, const std::string& key);
+
+template <typename DerivedMechPtr>
+inline std::vector<arb::fvm_value_type> mechanism_field(const std::unique_ptr<DerivedMechPtr>& m, const std::string& key) {
+    return mechanism_field(static_cast<arb::mechanism*>(m.get()), key);
+}
diff --git a/test/unit/mod/celsius_test.mod b/test/unit/mod/celsius_test.mod
new file mode 100644
index 0000000000000000000000000000000000000000..838ef243055f0fe9feb995b701a2e2cd6ac61f1b
--- /dev/null
+++ b/test/unit/mod/celsius_test.mod
@@ -0,0 +1,27 @@
+NEURON {
+    SUFFIX celsius_test
+}
+
+PARAMETER {
+    celsius
+}
+
+STATE {
+    c
+}
+
+ASSIGNED {
+}
+
+BREAKPOINT {
+    SOLVE states
+}
+
+DERIVATIVE states {
+    c = celsius
+}
+
+INITIAL {
+    c = 0
+}
+
diff --git a/test/unit/test_mech_temperature.cpp b/test/unit/test_mech_temperature.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8d152d9990fe131ff4fc91113721d163cb973911
--- /dev/null
+++ b/test/unit/test_mech_temperature.cpp
@@ -0,0 +1,80 @@
+#include <vector>
+
+#include <arbor/mechanism.hpp>
+#include <arbor/version.hpp>
+
+#include "backends/multicore/fvm.hpp"
+#ifdef ARB_GPU_ENABLED
+#include "backends/gpu/fvm.hpp"
+#endif
+
+#include "common.hpp"
+#include "mech_private_field_access.hpp"
+#include "unit_test_catalogue.hpp"
+
+using namespace arb;
+
+template <typename backend>
+void run_celsius_test() {
+    auto cat = make_unit_test_catalogue();
+
+    // one cell, three CVs:
+
+    fvm_size_type ncell = 1;
+    fvm_size_type ncv = 3;
+    std::vector<fvm_index_type> cv_to_cell(ncv, 0);
+
+    auto celsius_test = cat.instance<backend>("celsius_test");
+    auto shared_state = std::make_unique<typename backend::shared_state>(
+        ncell, cv_to_cell, celsius_test->data_alignment());
+
+    mechanism::layout layout;
+    layout.weight.assign(ncv, 1.);
+    for (fvm_size_type i = 0; i<ncv; ++i) {
+        layout.cv.push_back(i);
+    }
+
+    celsius_test->instantiate(0, *shared_state, layout);
+
+    double temperature_K = 300.;
+    double temperature_C = temperature_K-273.15;
+
+    shared_state->reset(-65., temperature_K);
+
+    // expect 0 value in state 'c' after init:
+
+    celsius_test->nrn_init();
+    std::vector<fvm_value_type> expected_c_values(ncv, 0.);
+
+    EXPECT_EQ(expected_c_values, mechanism_field(celsius_test.get(), "c"));
+
+    // expect temperature_C value in state 'c' after state update:
+
+    celsius_test->nrn_state();
+    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->nrn_init();
+
+    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) {
+    run_celsius_test<multicore::backend>();
+}
+
+#ifdef ARB_GPU_ENABLED
+TEST(mech_temperature_gpu, celsius) {
+    run_celsius_test<gpu::backend>();
+}
+#endif
diff --git a/test/unit/test_synapses.cpp b/test/unit/test_synapses.cpp
index 0c5464e698680d5375593857758975bd830ce05f..1428455d82203481e8376d225a99cde1fec7e080 100644
--- a/test/unit/test_synapses.cpp
+++ b/test/unit/test_synapses.cpp
@@ -15,6 +15,7 @@
 #include "util/range.hpp"
 
 #include "common.hpp"
+#include "mech_private_field_access.hpp"
 
 using namespace arb;
 
@@ -23,17 +24,7 @@ using shared_state = backend::shared_state;
 using value_type = backend::value_type;
 using size_type = backend::size_type;
 
-// Access to mechanisms protected data:
-using field_table_type = std::vector<std::pair<const char*, value_type**>>;
-ACCESS_BIND(field_table_type (multicore::mechanism::*)(), field_table_ptr, &multicore::mechanism::field_table)
-
-util::range<const value_type*> mechanism_field(std::unique_ptr<multicore::mechanism>& m, const std::string& key) {
-    if (auto opt_ptr = util::value_by_key((m.get()->*field_table_ptr)(), key)) {
-        const value_type* field = *opt_ptr.value();
-        return util::make_range(field, field+m->size());
-    }
-    throw std::logic_error("internal error: no such field in mechanism");
-}
+// Access to more mechanism protected data:
 
 ACCESS_BIND(const value_type* multicore::mechanism::*, vec_v_ptr, &multicore::mechanism::vec_v_)
 ACCESS_BIND(value_type* multicore::mechanism::*, vec_i_ptr, &multicore::mechanism::vec_i_)
@@ -74,6 +65,11 @@ static bool all_equal_to(const Seq& s, double v) {
     });
 }
 
+template <typename A, typename B>
+auto unique_cast(std::unique_ptr<B> p) {
+    return std::unique_ptr<A>(dynamic_cast<A*>(p.release()));
+}
+
 TEST(synapses, syn_basic_state) {
     using util::fill;
     using value_type = multicore::backend::value_type;
@@ -83,16 +79,10 @@ TEST(synapses, syn_basic_state) {
     int num_comp = 4;
     int num_cell = 1;
 
-    auto multicore_mechanism_instance = [](const char* name) {
-        return std::unique_ptr<multicore::mechanism>(
-            dynamic_cast<multicore::mechanism*>(
-                global_default_catalogue().instance<backend>(name).release()));
-    };
-
-    auto expsyn = multicore_mechanism_instance("expsyn");
+    auto expsyn = unique_cast<multicore::mechanism>(global_default_catalogue().instance<backend>("expsyn"));
     ASSERT_TRUE(expsyn);
 
-    auto exp2syn = multicore_mechanism_instance("exp2syn");
+    auto exp2syn = unique_cast<multicore::mechanism>(global_default_catalogue().instance<backend>("exp2syn"));
     ASSERT_TRUE(exp2syn);
 
     auto align = std::max(expsyn->data_alignment(), exp2syn->data_alignment());
diff --git a/test/unit/unit_test_catalogue.cpp b/test/unit/unit_test_catalogue.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..52a2f72c50adea56d7a97e9d7086b2d0ecfa3259
--- /dev/null
+++ b/test/unit/unit_test_catalogue.cpp
@@ -0,0 +1,28 @@
+#include <arbor/mechcat.hpp>
+#include <arbor/version.hpp>
+
+#ifdef ARB_GPU_ENABLED
+#include "backends/gpu/fvm.hpp"
+#endif
+#include "backends/multicore/fvm.hpp"
+
+#include "unit_test_catalogue.hpp"
+#include "mechanisms/celsius_test.hpp"
+
+#include "../gtest.h"
+
+using namespace arb;
+
+mechanism_catalogue make_unit_test_catalogue() {
+    mechanism_catalogue cat;
+
+    cat.add("celsius_test", mechanism_celsius_test_info());
+
+    cat.register_implementation("celsius_test", make_mechanism_celsius_test<multicore::backend>());
+#ifdef ARB_GPU_ENABLED
+    cat.register_implementation("celsius_test", make_mechanism_celsius_test<gpu::backend>());
+#endif
+
+    return cat;
+}
+
diff --git a/test/unit/unit_test_catalogue.hpp b/test/unit/unit_test_catalogue.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6516ffa23bcc03b6351041bd527a9b0f4abe5384
--- /dev/null
+++ b/test/unit/unit_test_catalogue.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <arbor/mechcat.hpp>
+
+arb::mechanism_catalogue make_unit_test_catalogue();
diff --git a/test/validation/CMakeLists.txt b/test/validation/CMakeLists.txt
index 8826d8e611e952f3ddae3fe154308c8fc1e18386..6257a38dccbf35aef0329bb1eb21523ed4c1d9b2 100644
--- a/test/validation/CMakeLists.txt
+++ b/test/validation/CMakeLists.txt
@@ -14,6 +14,7 @@ set(validation_sources
 )
 
 add_executable(validate ${validation_sources})
+target_compile_options(validate PRIVATE ${ARB_CXXOPT_ARCH})
 target_compile_definitions(validate PRIVATE "ARB_DATADIR=\"${ARB_VALIDATION_DATA_DIR}\"")
 target_link_libraries(validate PRIVATE gtest arbor arbor-aux ext-json)