Skip to content

Python ↔ Verilog Co-Simulation Guide

This guide documents the SC-NeuroCore co-simulation framework for validating that Verilog RTL generated by the equation compiler agrees with the Python reference under the metric appropriate to the model class. Deterministic non-chaotic models usually compare spike counts; discrete or sensitive maps use bounded state trajectories and event vectors.

Overview

The co-simulation framework:

  1. Compiles a model schema to Verilog via the equation compiler
  2. Generates a testbench that drives constant current for N clock cycles
  3. Compiles and runs the simulation via Icarus Verilog (iverilog + vvp)
  4. Captures the declared observable (spikes or a bounded state trace)
  5. Compares against the Python UniversalNeuron.step() reference
flowchart LR
    A["Schema<br/>(TOML)"] --> B["UniversalNeuron<br/>.from_schema()"]
    B --> C["Python<br/>simulation"]
    B --> D["to_verilog()"]
    D --> E["generate_testbench()"]
    E --> F["iverilog<br/>+ vvp"]
    C --> G{"Compare class<br/>observable"}
    F --> G
    G -->|match| H["✓ Verified"]
    G -->|mismatch| I["✗ Investigate"]

    style H fill:#e8f5e9
    style I fill:#ffcdd2

Prerequisites

  • Python 3.10+ with the sc_neurocore package installed
  • Icarus Verilog (iverilog, vvp) — install via apt install iverilog
  • Tests skip gracefully if iverilog is not available

Running Co-Simulation Tests

Bash
# Run all co-simulation tests
python -m pytest tests/test_cosimulation.py -v -s

# Run just the accuracy tests
python -m pytest tests/test_cosimulation.py -k "accuracy" -v -s

# Run Q4.12 precision tests
python -m pytest tests/test_cosimulation.py -k "Q412" -v -s

# Run Q16.16 precision tests
python -m pytest tests/test_cosimulation.py -k "Q1616" -v -s

Test Structure

The co-simulation suite is organized into four test classes:

TestCoSimulation — Q8.8 Baseline

Test Description Assertion
test_both_produce_spikes Both Python and Verilog spike (6 models) spikes > 0
test_spike_count_accuracy Spike counts match within 1% (6 models) gap < 1%
test_no_current_no_spikes Zero current → zero spikes (5 models) spikes == 0
test_python_sim_is_deterministic Python gives same result twice a == b
test_verilog_sim_is_deterministic Verilog gives same result twice a == b

TestQ412Precision — Q4.12 (16-bit, 12 fractional)

Test Description
test_lif_q412_spikes Q4.12 LIF produces spikes
test_lif_q412_near_python Q4.12 within 5% of Python
test_q412_vs_q88_comparison Both Q4.12 and Q8.8 within 5%
test_q412_zero_current_lif_is_range_classified LIF zero-current is excluded from Q4.12 parity by range diagnostics

Q4.12 is a narrow-range, high-resolution mode. It can track driven LIF spike counts in the existing test window, but it is not an mV-range LIF mode: v_rest=-65.0, initial v=-65.0, and tau_m=10.0 exceed the [-8, +7.9998] Q4.12 range. Use python -m sc_neurocore.neurons precision lif before treating any Q4.12 LIF run as a parity claim.

TestQ1616Precision — Q16.16 (32-bit, 16 fractional)

Test Description
test_lif_q1616_spikes Q16.16 LIF produces spikes
test_lif_q1616_near_python Q16.16 within 1% of Python
test_q1616_zero_current_silence Zero current → silence ✓

Verified Models

Six deterministic Q8.8 baseline models are verified. Five have exact spike-count parity; Izhikevich has a declared one-spike Q8.8 quantisation boundary and exact Q16.16 parity at the same operating point:

Model State Variables Complexity Spikes (I=50, 200 steps)
LIF v Linear 200
Lapicque v Linear 200
Quadratic IF v Quadratic 50
Izhikevich v, u Quadratic (2-var) 25 float64 / 24 Q8.8; 25 Q16.16
Resonate-and-Fire v, w Linear (2-var) 200
Perfect Integrator v Linear ramp 200

FitzHugh-Rinzel Q16.16 enrolment

The three-state fitzhugh_rinzel schema is validated separately from the Q8.8 baseline set because its slow variable and cubic RK4 datapath need Q16.16 range and resolution. The schema mirrors the hand model's coupled v, w, and y equations, rising-edge v >= 1 decision, and no-reset flow. Over 3000 steps, hand model, schema runner, and emitted RTL agree exactly on spike counts throughout the enrolled current band: seven crossings at I=0.4, eight at I=0.5, and eight at I=0.6. The marginal crossing at I=0.7 is outside the declared parity band because fixed-point rounding changes its count.

Hindmarsh-Rose Q16.16 enrolment

The three-state hindmarsh_rose schema now mirrors the maintained 1984 model: simultaneous classical RK4 over the cubic fast membrane, recovery, and slow adaptation equations; rising-edge x >= x_threshold observation; and no reset. The previous explicit-Euler schema carried an identity reset, which made the runtime treat detection="crossing" as a level decision and count every above-threshold timestep. The paired TOML/JSON schemas remove that semantic caricature.

Over 2,000 steps, the hand model, both schema formats, and emitted Q16.16 RTL agree exactly at five enrolled operating points: 0/0/26/40/52 crossings at I=0/2/3/4/5. This is a bounded behavioural contract, not indefinite chaotic trajectory identity. Over 5,000 steps at I=2/3/4/5, float64 reports 9/48/85/114 crossings while Q16.16 reports 10/49/86/115; dedicated tests preserve that one-crossing boundary explicitly.

The S5/H1 promotion emits a Q8.8 formal-catalogue core and port-only harness. Its depth-4 SymbiYosys/Z3 job proves reset-spike safety only; the Q16.16 operating points remain the behavioural parity evidence.

Pernarowski Q16.16 enrolment

The three-state pernarowski schema mirrors the maintained autonomous beta-cell burster: simultaneous classical RK4 over a cubic fast coordinate and two separated slow variables, rising-edge v >= 0.5 detection, and no reset. Its slow-wave rhythm is intrinsic, so input current shifts the trajectory rather than gating a silent/single/train response. Over 5000 steps, the hand model, schema runner, and emitted Q16.16 RTL agree exactly at all four enrolled operating points: 17 crossings at each of I=-0.1, 0.0, 0.1, and 0.2. The corresponding S5/H1 promotion also emits a Q8.8 formal-catalogue core and port-only harness. Its depth-4 SymbiYosys/Z3 job proves the bounded reset-spike safety property; the Q16.16 harness remains the behavioural parity evidence.

Terman-Wang Q16.16 enrolment

The two-state terman_wang schema mirrors the maintained LEGION relaxation oscillator: simultaneous classical RK4 over the cubic fast nullcline and tanh-gated recovery, rising-edge v >= 1.5 detection, and no reset. Because the recovery equation is transcendental, raw state bit identity is not portable across math libraries or fixed-point look-up tables. The enrolled observable is therefore the robust crossing count. Over 8,000 steps the hand model, schema runner, and Q16.16 RTL agree exactly on the full silent/single/train set: zero crossings at I=-1.0, one at I=0.0, and three at I=0.5.

The S5/H1 promotion also emits a Q8.8 formal-catalogue core and port-only harness. Its depth-4 SymbiYosys/Z3 job proves bounded reset-spike safety only; the three Q16.16 operating points remain the behavioural parity evidence.

Rulkov map Q16.16 trajectory enrolment

The rulkov_map schema mirrors the maintained Rulkov 2002 hand model as a simultaneous discrete recurrence: rational subthreshold branch, spike plateau, hard reset, slow y drift, and rising x >= 0 crossing detection. It is not an ODE and receives no timestep scaling or integrator smoothing.

The class-correct evidence is a bounded trajectory rather than a long-window spike count. At I=1.5 over 30 iterations, the rational, plateau, and reset branches execute ten times each. The hand model and paired TOML/JSON schemas agree exactly on every state and event. The Q16.16 RTL reproduces the complete ten-event vector, with both committed state coordinates within 0.001 absolute error of float64. This validates fixed-point lowering without claiming that a sensitive map must retain float64 trajectory identity indefinitely.

The S5/H2 descriptor additionally points to the raw Yosys 0.33 synth_xilinx report for the generated Q16.16 core. Its formal-catalogue entry uses generated Q8.8 RTL, a port-only harness, and a depth-4 SymbiYosys/Z3 reset-spike safety job; behavioural evidence remains the Q16.16 short-window trajectory.

Cazelles map Q16.16 trajectory enrolment

The cazelles_map schema mirrors the maintained Cazelles, Courbage, and Rabinovich (2001) hand model as a simultaneous discrete recurrence. The fast coordinate commits clip(a*x*(1-x) - y + I, -2, 2), the slow coordinate commits y + epsilon*(x - sigma) from the old state, and each committed x >= x_threshold value emits a level event. It is not a crossing detector and receives no ODE timestep scaling.

The enrolled Q16.16 evidence uses three bounded 30-iteration trajectories. At I=0.5, 1.0, and 2.0, the hand model and paired TOML/JSON schemas agree exactly on all states and events. The RTL reproduces the complete event vectors of two, one, and one events respectively, while both state coordinates remain within 0.0004 absolute error of float64. Together the points exercise the interior expression and both fast-state clip bounds.

The sensitive I=0.05 trajectory is an explicit exclusion: over 30 iterations, the hand/schema path emits seven level events while Q16.16 emits eight, with seven event-position mismatches. Pinning that observation prevents the bounded trajectory evidence from being read as long-window chaotic identity.

The S5/H1 promotion adds a Q8.8 formal-catalogue core and port-only harness. Its depth-4 SymbiYosys/Z3 job proves reset-spike safety only; the three Q16.16 trajectories remain the behavioural evidence.

Chialvo map Q16.16 event-class enrolment

The chialvo_map schema reproduces Chialvo (1995), DOI 10.1016/0960-0779(93)E0056-H: simultaneous x*x*exp(y-x) + k + I and a*y - b*x + c commits with method="map". The paper permits k to be constant or a time-dependent additive perturbation, so I carries the time-dependent part. The upward x_threshold=1.0 crossing is a maintained observation convention and is not attributed to the paper.

At I=-0.05/0/0.01/0.1/1.0 over 100 iterations, the hand model and paired TOML/JSON schemas agree exactly on every state and event. Q16.16 RTL preserves the event counts 0/2/3/0/1. At the stable I=-0.05/0.1/1.0 points, maximum absolute errors stay below 0.055 for x and 0.093 for y.

The exponential LUT phase-shifts four event positions at I=0 and six at I=0.01, although the total counts remain exact. Event timing and complete oscillatory trajectory identity are therefore explicit exclusions rather than hidden inside a loose tolerance.

The S5/H1 promotion adds a Q8.8 formal-catalogue core and port-only harness. Its depth-4 SymbiYosys/Z3 job proves the bounded reset/spike safety property; the Q16.16 operating set remains the behavioural evidence.

Aihara map Q8.24 bounded chaotic shadowing

The aihara_map schemas encode Aihara's reduced one-state Eqs. 10–11 and the Eq. 12 level waveform shaper. They do not retain the former unrelated two-state fast/recovery recurrence or reinterpret the level output as an upward crossing. The Figure 4 chaotic defaults are k=0.7, alpha=1, bias=0.3968, and epsilon=0.01, with y0=0.1.

Hand, TOML, and JSON trajectories agree exactly. The committed Q8.24 core preserves all 12 autonomous event decisions in the enrolled short horizon and keeps the internal-state error below 0.01. The short horizon is a scientific boundary: sigmoid LUT quantisation and binary64 exp differences amplify on a chaotic orbit, so long-window pointwise identity is not claimed.

The depth-6 SymbiYosys/Z3 job proves reset hygiene and the public Eq. 12 relation between the state sign and event output. A regression test also pins the explicit signed casts required at the generated sigmoid-LUT boundary.

Medvedev first-return Q16.16 enrolment

The medvedev_map schema is the scalar slow-calcium first-return reduction in Medvedev (2005), DOI 10.1016/j.physd.2005.01.021; it is not the superseded tent-map recurrence. Its three regions follow equations 4.4/4.7, 4.8 with 4.13, and 4.15. The paper does not tabulate one unique global pair of return functions, so the decay, affine-return, average, exponent, and scale constants are disclosed as SC-NeuroCore's reproducible calibration. I=0 is the sourced map; non-zero I is a maintained perturbation of active returns. An event is the maintained pre-step observation u <= u_HC, not a paper-defined spike.

The hand model and paired TOML/JSON schemas agree exactly on every state and event. At I=2 over 100 iterations they traverse the exact four-state cycle and emit the same 75-event vector. Q16.16 RTL preserves that complete vector with maximum state error below 0.007813. Q8.8 is not a valid target because the calibrated d=2271.1927977404063 log scale exceeds its signed range.

The emitted log datapath uses the shared 256-entry positive-domain LUT over [1/256, 8 + 1/256) at step 1/32. The S5/H1 promotion therefore emits a Q16.16 catalogue core and port-only harness; its depth-4 SymbiYosys/Z3 job proves bounded reset/event safety, while the 100-iteration co-simulation is the behavioural evidence.

Ibarz-Tanaka four-branch Q16.16 enrolment

The ibarz_tanaka_map schema implements Ibarz et al. (2007), Eqs. 2–3: constant, parabolic, plateau, and fixed -1 reset branches for v, plus the simultaneous u - mu*(v + 1 - sigma) slow update. I is the paper's I_v. The reset event is a pre-state level decision v >= 1 + I + u; no separate threshold, reset parameter, or beta belongs to this model.

At I=0.2 over 30 iterations, hand Python and paired TOML/JSON schema traces are identical. The protocol visits the constant branch initially, then 23 parabolic, four plateau, and three reset branches. Generated Q16.16 RTL preserves the complete three-event vector with maximum errors below 0.003 for v and 0.0001 for u.

Q8.8 is invalid because mu=0.001 quantises to zero. The S5/H1 catalogue job therefore uses Q16.16; its depth-4 SymbiYosys/Z3 proof establishes bounded reset-spike safety, while this 30-step trajectory is the behavioural evidence.

Courbage-Nekorkin map fixed-point trajectory enrolment

The courage_nekorkin_map schema reproduces Courbage, Nekorkin, and Vdovin (2007), equations 3–5: both coordinates commit simultaneously, the fast map uses its three published piecewise-linear branches, and the x >= d Heaviside term applies on the upper side of the discontinuity. I is the maintained API extension; I=0 is the published autonomous recurrence. The software event is an upward x >= x_threshold crossing and does not reset either coordinate.

At I=-0.3/0/0.3, the hand model and paired TOML/JSON schemas agree exactly on every state and event. Q16.16 RTL is event-exact over bounded 30/20/30-iteration windows. The points exercise all three fast-map branches and both sides of the Heaviside discontinuity; maximum coordinate errors remain below 0.014 for x and 0.00031 for y.

Q32.32 extends each input to 30 iterations and preserves the complete event vectors of one, four, and one events. Across that set, the maximum errors are 2.604e-5 for x and 8.379e-7 for y.

The autonomous 30-iteration Q16.16 trace is an explicit exclusion: float64 emits four events, RTL emits six, and six positions differ. Q32.32 resolves that same window at four events on both paths. The exclusion prevents bounded fixed-point evidence from being read as long-window identity for a sensitive discontinuous map.

The S5/H1 promotion adds a Q8.8 formal-catalogue core and port-only harness. Its depth-4 SymbiYosys/Z3 job proves reset-spike safety only; Q16.16/Q32.32 trajectories remain the behavioural evidence.

Ermentrout-Kopell theta-Euler Q16.16 enrolment

The ermentrout_kopell_map_neuron schema mirrors the maintained hand class, not the older catalogue theta schema. The sourced object is the continuous Ermentrout-Kopell (1986) theta equation. The hand implementation adds dt=0.1, input gain, forward Euler, an upward theta=pi event, and modulo 2*pi; the schema states those as maintained choices and commits the complete recurrence with method="map".

The event predicate uses theta_prev and the unwrapped candidate. This matters at negative current: the first candidate can fall below zero and commit near 2*pi, but that backward wrap is not an upward event. Positive-literal modulo is lowered with the same floored-remainder correction in Verilog and the generated integer C/Rust kernels.

The hand model and paired TOML/JSON schemas agree exactly on every float64 state and event under the varied-drive test. Over 2,000 steps, Q16.16 RTL preserves the class-correct spike counts at all enrolled points: zero at I=-0.5, 45 at I=0.5, and 64 at I=1.0. Maximum circular phase errors are respectively below 0.081, 0.089, and 0.025 rad. The cosine LUT shifts some event positions, so full fixed-point event vectors and trajectories are not claimed exact.

The integer C and Rust generators match Verilog state and event words cycle-for-cycle over 240 steps at both current signs. The S5/H1 promotion also emits a Q8.8 catalogue core and port-only harness; its depth-4 SymbiYosys/Z3 job proves the bounded reset/spike safety property only.

GLIF Q16.16 enrolment

The glif schema mirrors the maintained Allen Institute GLIF5 model: simultaneous classical RK4 over membrane voltage, adaptive threshold, and two after-spike currents, followed by candidate-level v >= theta detection and a candidate-first adaptive reset. A varied 4,000-step drive produces 181 resets; the hand model and both schema formats agree exactly on every event and all four post-step states.

Over 1,000 constant-current steps, hand model and schema runner report 0/0/23/54/86/95 spikes at I=0/15/22/30/45/50; emitted Q16.16 RTL reports the same six counts exactly. The compiler evaluates state-dependent resets from the integrated candidate and exposes post-reset output state, matching the schema runner's integrate-detect-reset order. This removes the former one-spike drift that had masked a pre-step-reset semantic mismatch. The enrolled hardware contract spans the whole operating set rather than one selected exact current.

The S5/H1 promotion retains the existing formal-catalogue enrolment with regenerated Q8.8 RTL, a port-only harness, and a depth-6 SymbiYosys/Z3 safety job. The Q16.16 six-point set remains the behavioural parity evidence.

Mihalas-Niebur Q16.16 enrolment

The mihalas_niebur schema mirrors the maintained four-state generalised integrate-and-fire model: simultaneous classical RK4 over membrane voltage, adaptive threshold, and two spike-triggered currents; candidate-level v >= theta detection; and a candidate-first reset that scales the membrane excursion, floors the threshold, and increments both currents. Over a varied 1,600-step sequence with 168 resets, the hand model and paired TOML/JSON schemas agree exactly on every event and all four post-step states.

Over 1,000 constant-current steps, hand model, schema runner, and emitted Q16.16 RTL agree exactly on 0/0/0/31/60/87/131/157/207/256 spikes at I=0/0.5/1/1.5/2/2.5/3.5/4/5/6. The former 300-step I=3 evidence is now exact at 36/36/36 after the shared candidate-reset/output correction. A longer 1,000-step run at the same current exposes one marginal fixed-point crossing: hand/schema/RTL report 111/111/112. The suite asserts that triplet separately, so the boundary cannot be hidden by a loose global tolerance or promoted as an exact operating point.

The S5/H1 descriptor retains the generated Q8.8 formal-catalogue core, port-only harness, and depth-3 SymbiYosys/Z3 safety job. The ten exact Q16.16 points plus the declared boundary remain the behavioural parity evidence.

Wilson-HR Q16.16 enrolment

The two-state wilson_hr schema mirrors the maintained polynomial cortical model: simultaneous classical RK4 over membrane v and recovery r, level v >= 0.4 detection, and a hard v = -0.7 reset that preserves the candidate recovery state. Five passes through eight 100-step current blocks produce 35 spikes and resets while both schema formats reproduce every post-step hand-model state exactly. Over 5,000 constant-current steps the hand model, schema runner, and Q16.16 RTL agree exactly on the enrolled silent/single/train set: zero spikes at I=0.0, one at I=2.0, and four at I=10.0.

The S5/H1 promotion also emits a Q8.8 formal-catalogue core and port-only harness. Its depth-4 SymbiYosys/Z3 job proves bounded reset-spike safety only; the three Q16.16 operating points remain the behavioural parity evidence.

Adding a New Model to Co-Simulation

  1. Ensure paired TOML/JSON schemas exist in src/sc_neurocore/neurons/model_schemas/.
  2. Create dedicated files named tests/test_cosim_<model>.py and tests/test_reference_<model>.py; do not add new enrolments to the legacy aggregate buckets.
  3. Run those model-specific tests — if they fail:
  4. Check whether a transcendental LUT changes the class-correct observable
  5. Check if parameters overflow the chosen precision mode
  6. Use python -m sc_neurocore.neurons precision <model> for diagnostics
Bash
python -m pytest -q \
    tests/test_cosim_your_new_model.py \
    tests/test_reference_your_new_model.py

Schema-Gap Reporting

Use the schema-gap report before selecting the next WC-A5 enrolment target:

Bash
python tools/schema_gap_report.py --format markdown --output docs/internal/schema_gap_report_latest.md

The tool scans live source modules and schema files without importing optional backends. It reports the net schema gap, source modules still lacking a same-name or alias schema, schema-only names, source-evidence classifications, and a ranked enrolment table. The current checkout has 154 model source modules, 32 unique schema models, a net schema gap of 122, and 124 source-module rows still needing same-name or alias schema coverage because izhikevich and lif are schema-only names.

Testbench Architecture

The generated testbench follows this structure:

Verilog
module tb_sc_lif;
    reg clk;
    reg rst_n;
    wire spike_out;
    wire signed [15:0] v_out;

    sc_lif uut (
        .clk(clk), .rst_n(rst_n),
        .I_t(16'sd12800),    // Q8.8 encoded input current
        .spike_out(spike_out),
        .v_out(v_out)
    );

    // 100 MHz clock
    initial clk = 0;
    always #5 clk = ~clk;

    integer spike_count;

    initial begin
        $dumpfile("tb_sc_lif.vcd");
        $dumpvars(0, tb_sc_lif);
        spike_count = 0;

        // Reset phase
        rst_n = 0;
        #20;
        rst_n = 1;
        @(posedge clk);        // 1 settling cycle

        // Measurement phase
        repeat (200) begin
            @(posedge clk);
            #1;                 // combinational settling
            if (spike_out)
                spike_count = spike_count + 1;
        end

        $display("Simulation complete: %0d spikes in 200 cycles",
                 spike_count);
        $finish;
    end
endmodule

Key timing details:

Phase Purpose Duration
Reset (rst_n = 0) Initialise all registers 20ns (2 clock periods)
Settling (@(posedge clk)) Allow reset to propagate 1 clock cycle
#1 after posedge Let combinational outputs settle 1ps (minimal)

Historical note: The settling cycle and #1 delay were added in 2026-05-01 to eliminate a 0.5% residual gap caused by sampling spike_out before combinational logic had propagated after reset de-assertion.

Compiler Correctness: Six Critical Fixes

The following fixes were applied to achieve 0.0% co-simulation accuracy:

# Fix Impact
1 Intermediate wire for bit-select iverilog compilation
2 Persistent wire counters Multi-variable models
3 Portable negative literals Cross-tool compatibility
4 Q-format division 99% → 50% gap
5 Look-ahead threshold 50% → 0.5% gap
6 Testbench timing 0.5% → 0.0% gap

Fix #4: Q-Format Division

Bug: Division by parameters used bare Verilog integer division (a / b), which divides by the Q-encoded raw value (e.g., 2560 for tau_m=10 in Q8.8).

Fix: Proper Q-format: (a << fraction) / b, with explicit sign extension:

Verilog
wire signed [15:0] _dop0 = numerator;                       // named operand
wire signed [31:0] _dnum0 = $signed({...}) <<< 8;           // shift up
wire signed [31:0] _div0 = _dnum0 / $signed({...});         // divide
wire signed [15:0] _dres0 = _div0[15:0];                    // extract Q result

Fix #5: Look-Ahead Threshold

Bug: Threshold compared v_reg (current cycle) instead of v_next (computed new voltage), creating a 1-cycle detection delay.

Fix: Threshold expression uses v_next wires:

Verilog
// Before: if ((v_reg > threshold))     ← 1 cycle late
// After:  if ((v_next > threshold))    ← matches Python semantics

Troubleshooting

Model produces spikes in Python but not in Verilog

  1. Check dt encoding: python -m sc_neurocore.neurons precision <model>
  2. If dt underflows (Q-value = 0), the Euler update is zero
  3. Solution: increase dt or use wider precision mode

  4. Check parameter range: Look for ⚠ Overflow or ⚠ Underflow warnings

  5. Solution: use Q16.16 for wide-range parameters

  6. Check division: Parameters used as divisors should be > 0.004 (Q8.8 minimum representable value)

Verilog produces more spikes than Python

  1. Check for Q-format overflow: Large v² products may wrap in 16-bit
  2. Solution: use Q16.16 for nonlinear models
  3. Example: Izhikevich at zero current — 0.04*v² overflows Q8.8

Compilation fails with iverilog

  • Ensure -g2012 flag is passed (SystemVerilog 2012 features required)
  • Check for bit-select on complex expressions (should be caught by the compiler)

Further Reading