Once upon a time, I wrote some code to identify a Cortex M core:

cpuid_t *cpuid = (cpuid_t*) (&SCB->CPUID);
if ((cpuid->PartNo & (0b11 << 10)) == (0b11 << 10)) {
  // Cortex family
  if ((cpuid->PartNo & (0b10 << 4)) == (0b10 << 4)) {
    // Cortex M
    int m = cpuid->PartNo & 0xF;
    int r = cpuid->Variant;
    int p = cpuid->Revision;

where cpuid_t is defined as

typedef union {
  uint32_t cpuid;
  struct {
    unsigned int Revision     :  4; // p value in r_p_
    unsigned int PartNo       : 12;      
    unsigned int Architecture :  4; 
    unsigned int Variant      :  4; // r value in r_p_
    unsigned int Implementer  :  8;
} cpuid_t;

As I looked at this code, I realised this implementation would not work for the more recent two-digit Cortex-M cores,
I traced what I based this code on, and I found the following definition for the PartNo in the Cortex M3 r1p1 reference manual.

Newer revisions of the Cortex M3 reference manual no longer explain the PartNo and simple define a value. Anyhow, this made me wonder about
what the newer revisions feature, and how they relate to the single digit variants and to each other.

White Paper: Cortex-M for Beginners – An overview of the Arm Cortex-M processor family and comparison

One of the key characteristics of the ISA in the Cortex-M processors is the upward compatibility: Instruction supported in the Cortex-M3 processor is a superset of Cortex-M0/M0+/M1. So theoretically if the memory map is identical, a binary image for Cortex-M0/M0+/M1 can run directly on a Cortex-M3. The same applies to the relationship between Cortex-M4/M7 and other Cortex-M processors; instructions available on Cortex-M0/M0+/M1/M3 can run on a Cortex-M4/M7.

While there is an update, covering some M23 and M33, but not yet M55 and M85, it contains the same paragraph. Therefore, the questions comes up how the single and double digit Cortex-M series come with compatibility.

One thing to consider is the Architecture field. This field contains 0xC for ARMv6m (Cortex M0, M0+ and M1) and ARMv8m-baseline (Cortex M23). The field contains 0xF for ARMv7m (Cortex M3, M4, M7) and ARMv8m-mainline (Cortex M33, M55, M85). This implies a Cortex M23 cannot run Cortex M7 code, thus breaking the higher number is upwards compatible characteristic. Would it be such that one should order them alphabetically instead? A Cortex M23 can run Cortex M0 code, after all, but could a Cortex M3 run Cortex M23 code?
A Cortex M23 has an optional feature, TrustZone, which is new and missing from the Cortex M3. When this extension is used, it means sorting them alphabetically rather then numerical does not uphold the upwards compatibility characteristic either.

Not considering optional features, both baseline and mainline profile of ARMv8m, add “Load acquire, store release” instructions. So regardless of extensions, the upward compatibility characteristic is broken. From what I gather, whether a double digit Cortex M can run code from a single digit Cortex M, the alphabetical ordering holds, but a single digit Cortex M cannot run code compiled for a double digit Cortex M, resulting in the following compatibility scheme:

                   +-- [ M23 ] --+-- [ M33 ] --+-- [ M55 ] --+-- [ M85 ]
[M0 / M0+ / M1]  -/--- [ M3  ] -/--- [ M4  ] -/--- [ M7  ] -/---

I guess, the upwards compatibility thing ain’t linear any more, once described as a key characteristic.