Skip to content
VLSI Mentor

CXL · Module 6

Protocol Use-Case Taxonomy

Which CXL protocol combination solves which problem, derived from five requirement questions rather than from a device category: why local memory does not imply CXL.mem, why reuse and not device class decides CXL.cache, and how to measure whether the choice was right. Five RTL models simulated, eleven mutations, eleven killed.

Chapter 6.1 established what each protocol obliges you to build. Chapter 6.2 established what it costs to run them together.

This chapter answers the question those two make askable: given a device to build, which of the three does it actually need?

1. The Engineering Problem — The Category Is Not the Requirement

A specification arrives: "a CXL accelerator with 64 GB of local memory." Three protocol decisions look settled by that sentence. None of them are.

  • It is an accelerator, so it caches host memory — only if the workload reuses what it fetches. Streaming data through once needs bandwidth, not coherence.
  • It has local memory, so it exposes it over .memonly if the host needs to reach it. Private working memory the host never touches needs no CXL protocol at all.
  • It is CXL, so it needs .ioyes. This is the one the category actually decides, and only because .io is mandatory for every device.

Chapter 3.2 went device type → engines. That is the right direction for understanding a device someone hands you. It is the wrong direction for designing one, because it starts from a label. This chapter runs the arrow backwards: requirements → protocol set → type, with the type as an output nobody chooses.

2. The One-Sentence Model

Ask five questions about the workload, and the protocol set falls out.io always; .cache only if the device must reach host memory and hold it coherently; .mem only if local memory must be host-visible. The device type is whatever those answers imply, and a protocol with no requirement behind it is cost with no owner.

Call it requirement-driven, not category-driven. RTL 1 is that sentence in logic, and its broken parameterisation is the category-driven alternative.

3. What This Chapter Owns

QuestionOwned by
Device types and their engines3.2
Direction — who may ask4.4
State obligation per protocol6.1
Coexistence, arbitration, starvation6.2
Requirement → protocol setthis chapter
The decision method and its costs6.4

The boundary with 6.4. This chapter derives what a workload needs. The next one is about how an architect runs that decision — the cost accounting, the over- and under-provision reviews, and the distinction between capability and enablement. Here the question is "which set?"; there it is "how do you defend the answer?".

4. The Wrong Way: Deriving Protocols From a Category

Before the framework, the failure it replaces. RTL 1 carries both, so the difference is measurable rather than asserted.

Icarus Verilog 13.0 — EXP2
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  device class                   | required  type    | category-driven
  managed device, no memory      |   001    io-only | 001
  caching accelerator            |   011    Type 1  | 011
  accelerator + visible memory   |   111    Type 2  | 111
  memory expander                |   101    Type 3  | 101
  private local mem, no coherence|   001    io-only | 111  <-- category says 111

The first four rows agree, which is exactly why category-driven selection survives so long: it is right whenever the category was chosen because of the requirements. The fifth row is the case that separates them — a device with local memory that the host never touches and host-memory access that needs no coherence. The requirement-driven answer is .io alone. The category-driven answer is all three.

That is two engines, two sets of state obligations on both ends, and two more classes contending in 6.2's arbiter, none of which any workload asked for.

5. The Five Questions

Everything in this chapter reduces to five questions about the workload, not the product.

#QuestionIf yes
1Must the device be discovered and configured?.io — and it is required anyway
2Must the device reach host memory?necessary for .cache, not sufficient
3...and hold it coherently, with reuse?.cache
4Does the device have local memory?necessary for .mem, not sufficient
5...must the host be able to reach it?.mem

The structure is two conjunctions, and both halves matter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
.cache  =  needs host memory   AND  needs coherence
.mem    =  has local memory    AND  host must see it

Question 2 without question 3 is a device that reads host memory once and streams it — that needs bandwidth, and a bulk transfer over .io may serve it better than a coherent engine. Question 4 without question 5 is private working memory: HBM on an accelerator that the host never addresses is not a .mem use case, it is just memory on a chip.

Mutations M1 and M2 delete one half of each conjunction, and both produce plausible, wrong device architectures.

Two requirement conjunctions feed the protocol mask. Needing host memory and needing coherence together produce CXL.cache. Having local memory and needing host visibility together produce CXL.mem. CXL.io is unconditional. The resulting mask determines the device type as an output.host mem ANDcoherentquestions 2 + 3any devicequestion 1local mem ANDhost-visiblequestions 4 + 5CXL.cacheoptionalCXL.iomandatoryCXL.memoptionaldevice typean OUTPUT, never aninput12

Both optional protocols hang off a conjunction, and both conjunctions have a first half that is necessary and not sufficient. Every arrow points downward: nothing in this diagram lets the device type influence the mask.

6. The Decision Table

NeedProtocolsType
configuration only.ionone
device caches host mem.io .cache1
host reaches device mem.io .mem3
both of the aboveall three2
local mem, host-private.ionone
host mem, streamed once.ionone

The last two rows are the ones a category-driven process gets wrong, and they are not exotic. A device can have gigabytes of local memory and still be an .io-only device, if the host never addresses that memory.

Note also that "none" appears three times. A device with no CXL device type is not a failure — it is a PCIe device on a CXL link, and 6.1 showed the three types enumerate only the coherent combinations.

7. Teaching-model boundary

8. RTL 1 — Requirements In, Protocol Mask Out

requirement_vector.sv — two conjunctions and one unconditional
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module requirement_vector #(
  parameter bit CATEGORY_DRIVEN = 1'b0   // 1 = choose from the device category
) (
  input  logic       clk, rst_n,
  input  logic       need_control,        // must be discovered/configured
  input  logic       need_host_mem,       // device must reach HOST memory
  input  logic       need_coherent,       // ...and hold it coherently
  input  logic       has_local_mem,       // device has attached memory
  input  logic       need_host_visible,   // ...that the HOST must reach
  input  logic [1:0] marketing_category,
  input  logic       valid,
  output logic [2:0] required_mask,       // {mem, cache, io}
  output logic [1:0] implied_type,
  output logic       io_omitted_err, cache_without_need_err,
  output logic       mem_without_visibility_err
);
  logic need_io_c, need_cache_c, need_mem_c;
  logic [2:0] derived, category_mask;
 
  // .io is required by ANY device: discovery and configuration are not optional.
  assign need_io_c    = 1'b1;
  // .cache is required only if the device must COHERENTLY reach host memory.
  // Reaching host memory without needing coherence is not a .cache requirement.
  assign need_cache_c = need_host_mem && need_coherent;
  // .mem is required only if local memory must be HOST-VISIBLE.
  // Local memory the host never touches needs no CXL protocol at all.
  assign need_mem_c   = has_local_mem && need_host_visible;
 
  assign derived = {need_mem_c, need_cache_c, need_io_c};
 
  // The bug shape: pick the mask from the product category.
  always_comb begin
    case (marketing_category)
      2'd1: category_mask = 3'b011;   // "accelerator"  -> io+cache
      2'd2: category_mask = 3'b111;   // "smart device" -> everything
      2'd3: category_mask = 3'b101;   // "memory"       -> io+mem
      default: category_mask = 3'b001;
    endcase
  end
 
  assign required_mask = valid ? (CATEGORY_DRIVEN ? category_mask : derived) : 3'b000;
 
  always_comb begin
    if      (required_mask[1] && required_mask[2]) implied_type = 2'd2;
    else if (required_mask[1])                     implied_type = 2'd1;
    else if (required_mask[2])                     implied_type = 2'd3;
    else                                           implied_type = 2'd0;
  end
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      io_omitted_err <= 1'b0; cache_without_need_err <= 1'b0;
      mem_without_visibility_err <= 1'b0;
    end else if (valid) begin
      if (!required_mask[0]) io_omitted_err <= 1'b1;
      // A protocol in the mask with no requirement behind it.
      if (required_mask[1] && !need_cache_c) cache_without_need_err <= 1'b1;
      if (required_mask[2] && !need_mem_c)   mem_without_visibility_err <= 1'b1;
    end
  end
endmodule

Purpose. To make protocol selection a derivation that can be checked, rather than a judgement recorded in a slide.

Invariants. Three, and each is a distinct review question. .io is present unconditionally; nothing appears in the mask without its requirement; and the type is an output.

Synthesis. Trivial — a handful of gates. This module is not really hardware; it is a specification made executable, and its value is that cache_without_need_err fires in simulation when someone widens the mask without widening the requirement.

Icarus Verilog 13.0 — EXP1, all 16 requirement combinations
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  hostmem coh localmem visible | mask  type
     0     0      0       0    | 001  io-only
     0     0      1       1    | 101  Type 3
     1     1      0       0    | 011  Type 1
     1     1      1       1    | 111  Type 2
  .io is in all 16 masks; .cache needs host-mem AND coherence;
  .mem needs local memory AND host visibility

9. RTL 2 — Under-Provision and Over-Provision Are Different Answers

requirement_vs_capability.sv — two independent questions
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module requirement_vs_capability (
  input  logic       clk, rst_n,
  input  logic [2:0] required_mask, device_mask,
  input  logic       valid,
  output logic [2:0] unmet,          // needed and absent
  output logic [2:0] spare,          // present and not needed
  output logic       satisfied, over_provisioned, usable,
  output logic       unmet_but_usable_err, spare_counted_as_unmet_err
);
  assign unmet  = valid ? (required_mask & ~device_mask) : 3'b000;
  assign spare  = valid ? (device_mask & ~required_mask) : 3'b000;
  assign satisfied        = valid && (unmet == 3'b000);
  assign over_provisioned = valid && (spare != 3'b000);
  assign usable = satisfied;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      unmet_but_usable_err <= 1'b0; spare_counted_as_unmet_err <= 1'b0;
    end else if (valid) begin
      if (usable && (unmet != 3'b000))     unmet_but_usable_err <= 1'b1;
      // Spare capability must never be reported as a gap.
      if ((unmet & device_mask) != 3'b000) spare_counted_as_unmet_err <= 1'b1;
    end
  end
endmodule
Icarus Verilog 13.0 — EXP3
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  need=111 dev=111 : unmet=000 spare=000 satisfied=1 over=0
  need=111 dev=011 : unmet=100 spare=000 satisfied=0 over=0  <-- gap
  need=101 dev=111 : unmet=000 spare=010 satisfied=1 over=1  <-- spare

unmet and spare are computed separately and mean opposite things. Unmet is a functional gap — the device cannot do the job. Spare is a cost with no owner — the device does the job and carries an engine nobody asked for, with all the state, timing and verification burden 6.1 enumerated.

Mutation M4 replaces the AND-NOT with XOR, which merges them: a spare protocol starts being reported as a missing one. The device is then rejected for having more capability than required, which is a spectacular way to fail a deployment check.

10. RTL 3 — Where the Crossover Actually Is

The question "does this device need coherent access or a bulk copy?" has a numeric answer, and it depends on reuse rather than on what the device is called.

access_cost_model.sv — teaching units, real crossover
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module access_cost_model #(
  parameter int unsigned SETUP_COPY   = 200,  // teaching units
  parameter int unsigned LAT_LOCAL    = 1,
  parameter int unsigned LAT_REMOTE   = 8,
  parameter bit          IGNORE_SETUP = 1'b0  // 1 = forget the copy's setup
) (
  input  logic        clk, rst_n,
  input  logic [15:0] n_access,
  input  logic        valid,
  output logic [31:0] cost_copy, cost_cache,
  output logic [1:0]  best,
  output logic        copy_wins, cost_order_err
);
  logic [31:0] c_copy, c_cache;
 
  // copy: pay setup once, then every access is local
  assign c_copy  = (IGNORE_SETUP ? 32'd0 : SETUP_COPY) + ({16'd0, n_access} * LAT_LOCAL);
  // cache: no setup, every access pays the remote cost
  assign c_cache = {16'd0, n_access} * LAT_REMOTE;
 
  assign cost_copy  = valid ? c_copy  : 32'd0;
  assign cost_cache = valid ? c_cache : 32'd0;
  assign copy_wins  = valid && (c_copy < c_cache);
  assign best       = copy_wins ? 2'd0 : 2'd1;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) cost_order_err <= 1'b0;
    else if (valid) begin
      // Stated about the COST, not about which side wins: with the setup
      // dropped both costs are zero at n=0 and a strict "copy_wins" comparison
      // is never true, so the check would be unreachable.
      if ((n_access == 16'd0) && (cost_copy == 32'd0)) cost_order_err <= 1'b1;
    end
  end
endmodule
Icarus Verilog 13.0 — EXP4
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  accesses | copy cost  cache cost  cheaper
     1     |    201         8       coherent
    20     |    220       160       coherent
    28     |    228       224       coherent
    29     |    229       232       copy
   100     |    300       800       copy
   500     |    700      4000       copy
  crossover: below ~29 accesses coherent access wins; above it, copy wins

The crossover is where SETUP + n·LAT_LOCAL = n·LAT_REMOTE, that is n = SETUP / (LAT_REMOTE − LAT_LOCAL) = 200/7 ≈ 28.6, so 29 is the first access count at which the copy wins. Both sides of that line are real device architectures, and the same silicon can be on the wrong side of it for a different workload.

The direction is worth stating carefully because it inverts a common intuition:

  • Low reuse — a few accesses per fetched line — favours coherent access. Paying a bulk-transfer setup to touch data twice is waste.
  • High reuse — many accesses per line — favours copying it local, because the setup amortises and every subsequent access is cheap.

So "the accelerator does a lot of work on the data, therefore it needs .cache" is backwards. Heavy reuse is the case that argues for moving the data; light, scattered, unpredictable access is the case that argues for coherence. Which is exactly why .cache is defined as caching host memory: it earns its area when the device cannot predict what it will need.

11. RTL 4 — Did the Workload Match the Assumption?

The taxonomy is a prediction made before silicon. This is how you find out whether it was right.

workload_profile.sv — with a conservation law
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module workload_profile #(
  parameter bit MISCOUNT = 1'b0     // 1 = drop device-memory events
) (
  input  logic        clk, rst_n, ev_valid,
  input  logic [1:0]  ev_kind,        // 0=control 1=host-mem 2=device-mem
  input  logic        ev_reused,
  output logic [15:0] n_control_q, n_host_mem_q, n_dev_mem_q, n_reuse_q, n_total_q,
  output logic        cache_justified, mem_justified, accounting_err
);
  /* ... counters, with n_total == control + host_mem + dev_mem ... */
 
  // Coherent caching of host memory earns its area only if the device both
  // touches host memory and REUSES it. Streaming once does not need a cache.
  assign cache_justified = (n_host_mem_q > 16'd0) && (n_reuse_q > 16'd0);
  assign mem_justified   = (n_dev_mem_q  > 16'd0);
endmodule
Icarus Verilog 13.0 — EXP5
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  streaming workload : control=30 host-mem=30 dev-mem=30 reuse=0
  cache_justified=0 mem_justified=1
  after 30 reusing accesses : reuse=30 cache_justified=1

A device can touch host memory constantly and still not justify a cache. Thirty host-memory accesses with zero reuse leaves cache_justified low — because the measurement that matters is not whether host memory is touched but whether it is touched again.

Mutation M6 drops the reuse term, so any host-memory traffic justifies the engine. That is precisely the argument that gets a .cache engine into a design that streams, and the counter exists to refute it with data.

12. RTL 5 — Refuse the Use Case You Cannot Serve

unsupported_usecase.sv — admission control on the requirement
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  assign ok     = deploy && ((required_mask & ~device_mask) == 3'b000);
  assign admit  = deploy && (ATTEMPT_ANYWAY ? 1'b1 : ok);
  assign reject = deploy && !admit;
Icarus Verilog 13.0 — EXP6
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  need=111 dev=101 : admit=0 reject=1 | attempt-anyway admit=1
  need=111 dev=111 : admit=1 reject=0

A Type 3 device offered a Type 2 use case must be refused at admission, not discovered at first coherent operation. Mutation M11 replaces the subset test with an overlap test — (required & device) != 0 — which admits any device sharing a single protocol with the requirement. Since .io is in every mask, that admits everything.

13. Assertions

Concurrent SVA execution: NOT SUPPORTED BY Icarus Verilog. Not executed; each maps to a procedural stand-in and a mutation.

use_case_taxonomy_sva.sv — bind-ready properties
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SAFETY -------------------------------------------------------------------
// V1 — .io is in every required mask, unconditionally.
a_io_always: assert property (@(posedge clk) disable iff (!rst_n)
  valid |-> required_mask[0]);
 
// V2 — .cache appears only with BOTH halves of its conjunction.
a_cache_needs_both: assert property (@(posedge clk) disable iff (!rst_n)
  required_mask[1] |-> (need_host_mem && need_coherent));
 
// V3 — .mem appears only with BOTH halves of its conjunction.
a_mem_needs_both: assert property (@(posedge clk) disable iff (!rst_n)
  required_mask[2] |-> (has_local_mem && need_host_visible));
 
// V4 — INDEPENDENCE: local memory alone never implies .mem.
//      The negative form is what catches an over-coupled derivation.
a_local_mem_insufficient: assert property (@(posedge clk) disable iff (!rst_n)
  (has_local_mem && !need_host_visible) |-> !required_mask[2]);
 
// V5 — the implied type matches the mask.
a_type_follows_mask: assert property (@(posedge clk) disable iff (!rst_n)
  (required_mask[1] && required_mask[2]) |-> (implied_type == 2));
 
// V6 — unmet and spare are disjoint by construction.
a_unmet_spare_disjoint: assert property (@(posedge clk) disable iff (!rst_n)
  valid |-> ((unmet & spare) == '0));
 
// V7 — spare capability is never reported as a gap.
a_spare_not_gap: assert property (@(posedge clk) disable iff (!rst_n)
  valid |-> ((unmet & device_mask) == '0));
 
// V8 — a device is usable only if nothing is unmet.
a_usable_iff_satisfied: assert property (@(posedge clk) disable iff (!rst_n)
  usable |-> (unmet == '0));
 
// V9 — a copy is never free.
a_copy_costs: assert property (@(posedge clk) disable iff (!rst_n)
  valid |-> (cost_copy > 0));
 
// V10 — CONSERVATION: workload events partition by kind.
a_workload_conserved: assert property (@(posedge clk) disable iff (!rst_n)
  n_total_q == n_control_q + n_host_mem_q + n_dev_mem_q);
 
// V11 — a use case whose needs exceed the device is refused.
a_admission: assert property (@(posedge clk) disable iff (!rst_n)
  admit |-> ((required_mask & ~device_mask) == '0));

V4 is the one worth copying into other work. V2 and V3 say what a protocol requires; V4 says what a signal is not sufficient for. Chapter 6.1 needed the same shape for a different reason — verification plans are full of "X requires Y" and almost empty of "Y alone does not give X", and that is where over-coupled derivations survive.

14. Mutation Testing

Eleven mutations. Clean code restored after each.

IDMutationResult
M1host-memory access alone implies .cacheKILLED — 16-case sweep
M2local memory alone implies .memKILLED — 16-case sweep
M3.io required only if control requestedKILLED — added stimulus
M4spare capability counted as a gapKILLED — spare_counted_as_unmet_err
M5every device usable for every requirementKILLED — unmet_but_usable_err
M6touching host memory justifies a cacheKILLED — reuse check
M7workload conservation disabledKILLED — miscounting variant
M8the copy's setup cost droppedKILLED — cost_order_err
M9copy always wins regardless of reuseKILLED — zero-access check
M10an unsupported use case admittedKILLED — admission check
M11any protocol overlap counts as supportKILLED — admission check
Mutation run — final
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
11/11 killed, 0 escaped

Two escaped on the first run.

M3 was a missing stimulus of an unusual shape — an input that was wired in and never varied. need_control was held high for the whole run, so making .io conditional on it changed nothing. §8's callout draws the general rule: an input present in the port list and constant in the testbench is not verified.

M7 was the unreachable-checker category. accounting_err can only fire if a counter is already wrong, so disabling it changes nothing on a correct design. Notably, adding an independent reference for every workload count did not kill it — the reference and the DUT both agreed, because the counts were right. Only a deliberately miscounting variant, whose accounting_err is required to fire, exercises the check.

That is worth separating clearly, because the two techniques get conflated:

An independent reference proves the counts are right. A positive test proves the checker works. They catch different bugs, and neither substitutes for the other.

15. Debug Lab

1

A memory expander ships without CXL.mem

REQUIREMENT-NOT-DERIVED
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// It's a memory product; the category says io + mem... but the mask was
// hand-written from the block diagram.
assign required_mask = 3'b001;   // io only
Symptom

A memory expander enumerates, reports its capacity in a vendor register, and the host cannot address a byte of it as system memory. The memory controller works; the DRAM trains; nothing errors.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  need=101 dev=001 : unmet=100 spare=000 satisfied=0
  a device without .mem cannot serve a host-visible-memory use case
Root Cause

The requirement — the host must address this memory — was never written down as a protocol requirement, so nothing derived .mem from it. The device has memory and a way to describe it, and no protocol by which the host can use it.

This is the under-provision half of §9: unmet is non-empty, and the device is simply not usable for the job it was built for.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign need_mem_c   = has_local_mem && need_host_visible;   // derive it
assign required_mask = {need_mem_c, need_cache_c, 1'b1};
// and check the device against it before deployment:
assign unmet = required_mask & ~device_mask;
Lesson

Write the requirement as a requirement, then derive. A protocol mask transcribed from a block diagram records what someone drew, not what the workload needs, and the two diverge silently. The check that catches it is cheap and mechanical: derive the mask from the requirement vector and compare it against what the device implements.

2

An accelerator carries a coherent cache its workload never uses

CACHE-WITHOUT-REUSE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// It's an accelerator, so it needs .cache.
assign need_cache_c = need_host_mem;
Symptom

Not a functional failure. Silicon ships, works, and the coherent engine shows near-zero traffic in production. The area, the timing pressure on the sub-200 ns path, and the verification effort all happened.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  streaming workload : host-mem=30 reuse=0
  cache_justified=0
Root Cause

The conjunction lost a half. Reaching host memory is necessary for .cache and not sufficient — a device that streams host memory through once gains nothing from holding it coherently, because it never looks at the line again.

§10's cost model gives the shape of the mistake: coherent access wins at low reuse, and a workload with heavy reuse is better served by moving the data. "The accelerator does a lot of work on the data" is an argument for copying, not for caching.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign need_cache_c = need_host_mem && need_coherent;
// and measure it after silicon:
assign cache_justified = (n_host_mem_q > 0) && (n_reuse_q > 0);
Lesson

Reuse, not device class, decides whether coherence earns its area. The engine costs state on both ends (6.1) and a quarter of the link under contention (6.2). The per-class counters that would prove it was worth having must ship in the current part, because the decision they inform is taken during the next one.

3

Private working memory is exposed over CXL.mem

LOCAL-MEMORY-ASSUMED-VISIBLE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The device has memory, so expose it.
assign need_mem_c = has_local_mem;
Symptom

An accelerator with private working memory is built as a Type 2 device. The host maps memory it never uses. The design carries an HDM decode path, host-managed coherency for a region nothing shares, and the full Type 2 verification matrix.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  private local mem, no coherence | requirement-driven: 001 | category-driven: 111
  local memory alone does NOT imply .mem : ok
Root Cause

The second half of the .mem conjunction was dropped. Local memory is necessary for .mem and not sufficient — the question is whether the host must reach it. HBM used only by the accelerator's own datapath is memory on a chip, not a CXL resource.

The cost is not just the engine. Exposing memory makes it host-managed, which pulls in coherence obligations for a region that had none, and moves the device from "no CXL type" to Type 2 — the hardest class to verify.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign need_mem_c = has_local_mem && need_host_visible;
if (required_mask[2] && !need_mem_c) mem_without_visibility_err <= 1'b1;
Lesson

Ownership is not visibility. A device owning memory says nothing about whether anyone else needs to address it, and conflating the two is the single most expensive step in this taxonomy — it is the difference between an .io-only device and a Type 2. The assertion form is the independence property: local memory without host visibility must not produce .mem.

4

Protocol set chosen from the product category

CATEGORY-DRIVEN-SELECTION
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
case (marketing_category)
  2'd2: required_mask = 3'b111;   // "smart device" -> everything
  ...
endcase
Symptom

A device is specified with all three protocols because of what it is called. Two engines have no workload behind them. The failure appears at schedule review, when the verification matrix for a Type 2 device is estimated for a device whose workload is .io-only.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  private local mem, no coherence | required 001 | category-driven 111
  category-driven picks 3 protocols, needs 1
Root Cause

The category was used as the input to the protocol decision rather than as a label applied afterwards. It survives because categories are usually chosen for the right reasons — §4 shows four of five archetypes agreeing — so the process appears to work until it meets a device whose category and requirements diverge.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Derive from requirements; let the category be the output's name.
assign required_mask = {has_local_mem && need_host_visible,
                        need_host_mem  && need_coherent,
                        1'b1};
if (required_mask[1] && !need_cache_c) cache_without_need_err <= 1'b1;
Lesson

A category is a summary of a decision, not an input to it. The tell in a review is a protocol whose justification is a noun — "it's an accelerator", "it's a memory device" — rather than a sentence about what the workload does. Chapter 6.4 turns that tell into a standing review question.

5

A device is rejected for having too much capability

SPARE-COUNTED-AS-GAP
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Anything that differs is a mismatch.
assign unmet = required_mask ^ device_mask;
Symptom

A Type 2 device is refused for a memory-expansion deployment that needs only .io + .mem. The device is fully capable and carries .cache as well. Deployment tooling reports a capability mismatch and will not admit it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  need=101 dev=111 : unmet=000 spare=010 satisfied=1 over=1   (correct)
  XOR version      : unmet=010 -> reported as a gap
Root Cause

unmet was computed with XOR rather than AND-NOT, which conflates two opposite conditions. A protocol the device has and the requirement does not need is spare — a cost, possibly a bad architectural choice, and not a reason to refuse a working deployment.

The two are computed separately for exactly this reason: they mean opposite things and lead to opposite actions.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign unmet = required_mask & ~device_mask;   // needed and absent
assign spare = device_mask & ~required_mask;   // present and not needed
if ((unmet & device_mask) != 3'b000) spare_counted_as_unmet_err <= 1'b1;
Lesson

Under-provision and over-provision are separate answers to separate questions. A single "match" boolean cannot express either, and XOR is the specific bug that turns surplus into deficit. The diagnostic that catches it — a bit reported unmet that the device demonstrably has — is a one-line invariant.

6

A workload change invalidates the protocol choice after tapeout

ASSUMPTION-NEVER-MEASURED
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Count work done.
if (ev_valid) n_total_q <= n_total_q + 16'd1;
Symptom

Two years after shipping, the workload has shifted from scattered host-memory access to bulk streaming. Nobody can say whether the .cache engine is still earning its place, because the only telemetry is a total event count.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  merged counter : total=90 and nothing else
  per-kind       : control=30 host-mem=30 dev-mem=30 reuse=0
Root Cause

The architectural assumption — that the device reuses host memory — was never instrumented. It was true when the part was specified and stopped being true, and no measurement existed to notice.

Note the specific gap: reuse is the term that decides, and it is the one a generic transaction counter never captures. Host-memory access counts alone cannot distinguish a workload that justifies coherence from one that does not.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
case (ev_kind)
  2'd0: n_control_q  <= n_control_q  + 16'd1;
  2'd1: n_host_mem_q <= n_host_mem_q + 16'd1;
  2'd2: n_dev_mem_q  <= n_dev_mem_q  + 16'd1;
endcase
if (ev_reused) n_reuse_q <= n_reuse_q + 16'd1;
assign cache_justified = (n_host_mem_q > 0) && (n_reuse_q > 0);
Lesson

Instrument the assumption, not just the traffic. Every protocol in the mask rests on a claim about the workload, and each claim needs a counter that could falsify it. The specific one here is reuse — without it, a design can watch heavy host-memory traffic and conclude, wrongly, that its coherent engine is busy doing useful work.

16. Verification Plan

ItemApproach and goal
Requirement derivationsweep all 16 requirement combinations — mask matches the conjunctions
Unconditional .iodrive need_control = 0.io still present
Independencelocal memory without visibility — .mem must not appear
Category divergencea case where category and requirement disagree — they must differ
Unmet vs spareexact match, gap, and surplus — three distinct classifications
Cost crossoversweep access counts across the crossover — the winner flips once
Zero accessesn_access = 0 — a copy is never free
Workload measurementstreaming then reusing — cache_justified flips only on reuse
Independent referenceevery workload count vs a stimulus-derived reference
Admissiona device missing a required protocol — refused, not attempted
Diagnostic livenessbroken variants incl. a miscounting profiler — each observed firing

Rows 2, 3 and 9 are this chapter's additions. Row 2 exists because a constant input is an unverified input; row 3 because independence properties are the ones nobody writes; row 9 because an independent reference and a positive test on a checker catch different bugs, and this chapter needed both.

17. Design Review

  • For each protocol in the mask, what sentence about the workload justifies it? A noun is not an answer.
  • Does anything derive .cache from host-memory access alone, or .mem from local memory alone?
  • Is the device type an input anywhere, or is it always an output?
  • Are under-provision and over-provision reported as separate results?
  • What is the expected reuse, and which counter will confirm it after silicon?
  • If the workload shifts, which measurement tells you the protocol choice has gone stale?
  • Is a device with surplus capability admitted or refused? Which is intended?

18. How This Appears in Real Engineering

Category-driven selection survives because it is usually right. Four of §4's five archetypes agree with the requirement-driven answer. The process only fails on the device whose category was chosen for marketing reasons — and that is the device where the cost is largest, because the extra engines have no workload at all.

The cost model's direction surprises people. Heavy reuse argues for copying, not caching. Teams reasoning that "our accelerator works hard on the data, so it needs coherent access" have the crossover backwards, and the resulting .cache engine sits idle while a DMA path does the work.

Private memory becomes public by default. Debug Lab 3 is common because exposing device memory sounds free — it is "just" advertising an address range. It moves the device from no CXL type to Type 2 and pulls in host-managed coherency for a region nothing shares.

Nobody instruments reuse. Transaction counters are standard; reuse counters are not. The question that decides the next generation's protocol set is exactly the one the current generation cannot answer.

19. Common Misconceptions

ClaimWhy it is wrong
"An accelerator needs .cache"Only if it reaches host memory and needs coherence. Streaming once needs bandwidth.
"A device with local memory needs .mem"Only if the host must reach that memory. Private working memory needs no CXL protocol.
"Heavy data reuse argues for coherent access"Backwards. Heavy reuse amortises a copy; low, unpredictable reuse is what coherence serves.
"The device type tells you the protocols"The protocols tell you the type. Running it the other way starts from a label.
".io follows from a stated control requirement".io is mandatory for all devices, requirement or not.
"More capability is never a problem"Spare capability is state, timing pressure and DV surface with no workload owner.
"A capability mismatch means the device cannot be used"Only if something is unmet. Surplus is not a gap.
"The Consortium's usage lists are a taxonomy"They are examples of what people built, not a derivation of what makes a device need a protocol.

20. Interview Reasoning

21. Exercises

  1. Derive. A device must: be configured by the host; read host-resident lookup tables with heavy reuse; and hold 32 GB of packet buffers the host never addresses. Give the required protocol mask, the implied type, and the one requirement change that would move it to Type 2.

  2. Calculate. Using §10's model, find the crossover access count for SETUP = 500, LAT_LOCAL = 2, LAT_REMOTE = 10. Then state what happens to the crossover as the remote/local latency ratio falls, and what that implies for a device on a lower-latency link.

  3. DV task. Write the two independence properties for this chapter's conjunctions. Explain why every positive "X requires Y" test in the plan passes on a design that violates them.

  4. RTL task. Extend requirement_vector so a requirement can be anticipated but not current — a workload the device should support next generation. State which existing diagnostic becomes ambiguous and what new one is needed.

  5. Debug task. A deployment tool refuses a Type 2 device for a memory-expansion role. Give your investigation order, and name the single expression most likely at fault.

  6. Critique. Argue that a device should implement .mem whenever it has local memory, on the grounds that host visibility might be wanted later. Give the strongest case, then price it using 6.1's state obligations and 6.2's arbitration shares.

22. Summary

The protocol set is derived from the workload, and the device type is an output.

  • Five questions, forming two conjunctions and one unconditional: .io always; .cache needs host-memory access and coherence; .mem needs local memory and host visibility.
  • Local memory does not imply .mem. Ownership is not visibility, and conflating them is the step between an .io-only device and a Type 2.
  • Reuse, not device class, decides .cache — and the crossover runs the counterintuitive way: low reuse favours coherent access, heavy reuse favours copying.
  • Under-provision and over-provision are different answers. A device with surplus capability is usable; XOR is the bug that turns surplus into deficit.
  • The Consortium's usage lists are examples for checking a derivation, never a substitute for one.
  • Instrument the assumption, not the traffic — reuse is the counter that decides, and it is the one nobody adds.
  • Verification lessons: an input that is never varied is not verified; independence properties catch what positive tests cannot; and an independent reference and a positive test on a checker catch different bugs.

Chapter 6.4 closes Module 6 by turning this derivation into a decision method — what each capability costs, how to detect over- and under-provision in a real architecture, and why "hardware supports it" and "the system enables it" are different statements.

Standards & specifications

Governing standard
CXL Specification (CXL Consortium)(opens CXL Consortium in a new tab)

Defines CXL.io, CXL.cache and CXL.mem, and the coherence and memory-pooling behaviour built on them. System design and deployment topology are not mandated.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the CXL curriculum.