Documentation menu

Tutorial: your first system model

In this tutorial you model a home heating system from a blank page: a thermostat on the wall measures the room temperature and a boiler control board fires the burner when the room is too cold. You go through every level of the Arcadia method, link the levels with traces, add tests, and finish with a model that passes the production gate.

It takes about 45 minutes. No installation is needed: everything runs in your browser. If MBSE is new to you, read MBSE in 15 minutes first; it explains the terms used here.

Each step shows the ArcLang text that the step adds. Every sample on this page is compiled when the site is built, so it is exactly what the compiler accepts. The complete model is at the end of the page.

Open a blank model

  1. Open the playground on a computer. It opens VS Code for the Web, with the ArcLang extension. (On a phone, the playground opens a lighter editor without the workbench.)
  2. Open the command palette (⌘⇧P on macOS, Ctrl+Shift+P elsewhere) and run the command ArcLang: New model… from it.
  3. Choose Blank model and name the file home_heating.arc.

The editor shows the text on the left and the workbench on the right. The workbench draws the model; the text is the model. Both stay in step: a box you draw is written into the text, and a line you type is drawn.

The new file holds only a header:

heating_1.arc
model HomeHeating {
  name: "Home heating"
  version: "0.1"
}

The workbench says the model is empty and offers Add a level…. Each level of the method opens an empty sheet with a palette: the tools that create the elements of that level. You use it at every step below.

A word on identifiers. Every element has an id, a stable name that other elements use to point at it. The workbench gives each element you draw an id, and you can change it in the Properties form. This page uses short readable ids such as SF-MEASURE (SF for system function). Use the same ones if you want your text to match the samples.

Step 1: write the requirements

Start with what the system must achieve. Click Add a level… and choose Requirements. An empty requirements block appears in the text. Type the three requirements into it:

heating_2.arc
requirements {
  req "REQ-001" "Comfort" {
    description: "The system shall keep the living room within 0.5 °C of the set temperature"
    priority: "High"
  }
  req "REQ-002" "Reaction time" {
    description: "The system shall start or stop the burner within 2 s of a reading that crosses the set temperature"
    priority: "Medium"
  }
  req "REQ-003" "Set temperature" {
    description: "The occupant shall be able to change the set temperature on the thermostat"
    priority: "Medium"
  }
}

Each req has an identifier, a title, a description written as a checkable statement ("shall"), and a priority. Identifiers that contain a hyphen are written between quotes.

Step 2: operational analysis

The operational analysis describes the need, before any solution: who is involved (actors and entities), what they do (activities), and what passes between them (interactions). Here, the occupant tells the home what temperature they want, and the home keeps the rooms warm.

Click Add a level… and choose Operational. The sheet that opens has a palette with Actor, Entity, Activity, Capability and Interaction.

  1. Pick Actor and name it Occupant.
  2. Pick Entity and name it Home.
  3. Select the Home box, pick Activity and name it Take the wished temperature. Do it again for Keep the rooms warm. An activity created on a selected entity is performed by it, so it is written inside the entity.
  4. Pick Interaction, click Occupant, then the first activity, and name it Wished temperature. Draw a second interaction from the first activity to the second.
  5. Pick Capability and name it Live in a warm home. An operational capability is what the actors achieve together.

The text now holds the whole level. Fill the descriptions and the capability's involves list in the Properties form or directly in the text:

heating_3.arc
operational_analysis "Operational Analysis" {
  operational_capability "Live in a warm home" {
    id: "OC-WARM"
    description: "The home stays at the temperature its occupants want"
    involves: ["OA-OCCUPANT", "OA-HOME"]
  }
  actor "Occupant" {
    id: "OA-OCCUPANT"
    description: "A person who lives in the home"
  }
  entity "Home" {
    id: "OA-HOME"
    description: "The house, its rooms and its boiler"
    activity "Take the wished temperature" { id: "OA-WISH" }
    activity "Keep the rooms warm" { id: "OA-WARM" }
  }
  interaction "Wished temperature" { from: "OA-OCCUPANT" to: "OA-WISH" }
  interaction "Temperature to reach" { from: "OA-WISH" to: "OA-WARM" }
}

Nothing here mentions a thermostat or a boiler. That is the point of this level.

Step 3: system analysis

The system analysis looks at the system as a black box: what it must do for its users, not how. You describe system functions (verbs), the functional exchanges between them, and the capability they provide.

Click Add a level… and choose System. Its palette has Actor, Function, Port in, Port out, Mission, Capability, Functional chain and Functional exchange, among others.

  1. Pick Actor and name it Occupant. Each level has its own actors.
  2. Pick Function four times: Measure the room temperature, Take the set temperature, Decide the heating demand, Drive the burner.
  3. Pick Functional exchange, click Measure the room temperature, then Decide the heating demand, and name it room temperature. The workbench declares an output port on the first function and an input port on the second. Draw set temperature from Take to Decide, and heating demand from Decide to Drive.
  4. Pick Mission (Comfort at home) and Capability (Heat the home). A mission is a high-level goal; a capability is what the system can do to serve it.
  5. Pick Functional chain and name it Reading to burner.

Then add, in the Properties form or the text, the latency of each function and the chain's latency budget:

heating_4.arc
system_analysis "System Analysis" {
  actor "Occupant" { id: "SA-OCCUPANT" }
  mission "Comfort at home" { id: "MIS-COMFORT" }
  capability "Heat the home" {
    id: "CAP-HEAT"
    mission: "MIS-COMFORT"
    realizes: "OC-WARM"
    involves: ["SA-OCCUPANT", "SF-MEASURE", "SF-SET", "SF-DECIDE", "SF-BURN"]
  }
  function "Measure the room temperature" {
    id: "SF-MEASURE"
    latency: 500 ms
    port out temperature
  }
  function "Take the set temperature" {
    id: "SF-SET"
    port out setpoint
  }
  function "Decide the heating demand" {
    id: "SF-DECIDE"
    latency: 200 ms
    port in temperature
    port in setpoint
    port out demand
  }
  function "Drive the burner" {
    id: "SF-BURN"
    latency: 1 s
    port in demand
  }
  functional_exchange "room temperature" { id: "FE-TEMP" from: "SF-MEASURE.temperature" to: "SF-DECIDE.temperature" }
  functional_exchange "set temperature" { id: "FE-SET" from: "SF-SET.setpoint" to: "SF-DECIDE.setpoint" }
  functional_exchange "heating demand" { id: "FE-DEMAND" from: "SF-DECIDE.demand" to: "SF-BURN.demand" }
  functional_chain "Reading to burner" {
    id: "FC-HEAT"
    involves: ["SF-MEASURE", "SF-DECIDE", "SF-BURN"]
    latency_budget: 2 s
  }
}

Read the new parts:

  • latency: 500 ms is a typed quantity, not text. The compiler knows it is a duration, and converts 1 s and 500 ms to the same unit.
  • A functional chain is an ordered path through functions. involves lists them in order, and latency_budget: 2 s is the time the path may take. This is requirement REQ-002 written in a form the compiler can evaluate.
  • realizes: "OC-WARM" says this capability realizes the operational capability of step 2. It is the first link between two levels.

Check the model now: in the workbench the findings list is empty, and with the compiler installed on your computer you would see the same:

$ arclang check heating_4.arc
Checking heating_4.arc...
✓ No compilation errors

⚠ Traceability warnings:
  Requirement REQ-001 has no trace
  Requirement REQ-002 has no trace
  Requirement REQ-003 has no trace

The warnings say that no trace reaches the requirements yet. Step 6 fixes that.

Step 4: logical architecture

The logical architecture opens the box. You split the system into logical components, still without choosing technology, and you allocate to each one the functions it performs.

Click Add a level… and choose Logical. Its palette has Component, Function, Port in, Port out, Interface, Component exchange and Functional exchange.

  1. Pick Component three times: Thermostat, Heating controller, Burner unit.
  2. Select Thermostat, pick Function and name it Sense the temperature. A function created inside a component is allocated to it: the component performs it. Add Show and set the temperature to the thermostat, Compute the demand to the controller, and Fire the burner to the burner unit.
  3. Pick Component exchange and draw Readings and set point from the thermostat to the controller, then Burner command from the controller to the burner unit. The workbench declares the ports at both ends.
  4. In the Properties form of each logical function, set realizes to the system function it realizes, and copy its latency.
heating_5.arc
logical_architecture "Logical Architecture" {
  component "Thermostat" {
    id: "LC-THERMO"
    function "Sense the temperature" { id: "LF-SENSE" realizes: "SF-MEASURE" latency: 500 ms }
    function "Show and set the temperature" { id: "LF-SET" realizes: "SF-SET" }
    port out readings
  }
  component "Heating controller" {
    id: "LC-CTRL"
    function "Compute the demand" { id: "LF-DECIDE" realizes: "SF-DECIDE" latency: 200 ms }
    port in readings
    port out command
  }
  component "Burner unit" {
    id: "LC-BURNER"
    function "Fire the burner" { id: "LF-BURN" realizes: "SF-BURN" latency: 1 s }
    port in command
  }
  component_exchange "Readings and set point" { id: "CE-READINGS" from_port: "LC-THERMO.readings" to_port: "LC-CTRL.readings" }
  component_exchange "Burner command" { id: "CE-COMMAND" from_port: "LC-CTRL.command" to_port: "LC-BURNER.command" }
}

Each level has functions of its own. Sense the temperature is a logical function that realizes the system function Measure the room temperature; nesting it inside Thermostat is the allocation.

Step 5: physical architecture

The physical architecture says what the system is really made of: nodes (hardware that hosts software), the logical components deployed on them, and the physical links between nodes.

Click Add a level… and choose Physical. Its palette has Node, Behavior component, Hardware component, Function, the port tools, Physical link, Physical exchange and Functional exchange.

  1. Pick Node twice: Wall thermostat and Boiler control board.
  2. Pick Physical link, click the two nodes, and name it Radio link.
  3. In the text, add the deploys lines: the thermostat component runs on the wall thermostat; the controller and the burner unit run on the control board. Add the link's protocol and bandwidth.
heating_6.arc
physical_architecture "Physical Architecture" {
  node "Wall thermostat" {
    id: "PN-WALL"
    processor: "Low-power microcontroller"
    deploys "LC-THERMO"
  }
  node "Boiler control board" {
    id: "PN-BOARD"
    deploys "LC-CTRL"
    deploys "LC-BURNER"
  }
  physical_link "Radio link" {
    id: "PL-RADIO"
    from: "PN-WALL"
    to: "PN-BOARD"
    protocol: "868 MHz radio"
    bandwidth: 20 kbps
  }
}

deploys must name a logical component that exists: a misspelled id stops the build. bandwidth: 20 kbps is a data rate, another typed quantity.

Step 6: traces

The levels are written. Now link them with traces, so that every element is tied to the reason it exists:

  • realizes goes from a lower level to the level above: a system function realizes an operational activity;
  • satisfies goes from a component to a requirement.

In the workbench, pick Trace in the palette of a sheet, click the source and the target, and type the kind (realizes is proposed). Traces between levels are easier to type in the text, after all the blocks:

heating_7.arc
trace "SF-SET" realizes "OA-WISH"
trace "SF-DECIDE" realizes "OA-WARM"
trace "LC-CTRL" satisfies "REQ-001" { rationale: "Closes the loop between the reading and the set point" }
trace "LC-BURNER" satisfies "REQ-002" { rationale: "Fires the burner at the end of the chain" }
trace "LC-THERMO" satisfies "REQ-003" { rationale: "The set point is changed on the thermostat" }

rationale is free text for the reviewer: why this component meets this requirement.

$ arclang check heating_7.arc
Checking heating_7.arc...
✓ No compilation errors

Every requirement is now satisfied by a component, and the check reports no warning.

Step 7: run the production gate

Compiling is not the same as being ready. The production gate answers a different question: is this model complete enough for a design review? Run it:

$ arclang gate heating_7.arc
Production-readiness gate (ISO26262) — heating_7.arc
  Requirements: 3 total, 3 satisfied, 0 verified

✗ 3 blocker(s):
  [requirements.verification] requirement 'REQ-001' has no verification case (test_case verifies: [...])
  [requirements.verification] requirement 'REQ-002' has no verification case (test_case verifies: [...])
  [requirements.verification] requirement 'REQ-003' has no verification case (test_case verifies: [...])

PRODUCTION GATE: FAIL ✗ (3 blockers)
Error: Compilation error: production gate failed with 3 blocker(s)

The gate fails, with three blockers: the requirements are satisfied, but nothing verifies them. A requirement that nobody checks is a promise, not a requirement.

Step 8: add the test cases

A test_case states how one or more requirements are verified, with a method: test, analysis, inspection or demonstration.

REQ-002 can be verified on the model itself. A constraint is an assertion the compiler evaluates: here, the sum of the latencies along the chain must not exceed its budget. The test case TC-002 is an analysis that asserts this constraint, so its verdict is computed on every build.

heating_8.arc
constraint "Chain within budget" {
  id: "CST-HEAT"
  assert: sum("FC-HEAT", latency) <= "FC-HEAT".latency_budget
}

test_case "TC-001" {
  verifies: ["REQ-001"]
  method: "test"
  description: "Record the room temperature for 24 h in a test house"
}
test_case "TC-002" {
  verifies: ["REQ-002"]
  method: "analysis"
  asserts: ["CST-HEAT"]
  description: "The latencies along the chain Reading to burner fit its budget"
}
test_case "TC-003" {
  verifies: ["REQ-003"]
  method: "demonstration"
  description: "Change the set point on the thermostat and watch the boiler react"
}

Step 9: check and gate

Run the check and the gate again:

$ arclang check heating_8.arc
Checking heating_8.arc...
✓ No compilation errors

Constraints:
  ✓ Chain within budget — sum("FC-HEAT", latency) <= "FC-HEAT".latency_budget  [1.7 s vs 2 s]

$ arclang gate heating_8.arc
Production-readiness gate (ISO26262) — heating_8.arc
  Requirements: 3 total, 3 satisfied, 3 verified
  Constraints: 1 declared, 1 hold

PRODUCTION GATE: PASS ✓

The check evaluates the constraint: 500 ms + 200 ms + 1 s = 1.7 s, within the 2 s budget. The gate passes: three requirements, three satisfied, three verified.

Try breaking it. Change the latency of Drive the burner from 1 s to 1.5 s (here in a copy, slow.arc) and run the gate:

$ arclang gate slow.arc
Production-readiness gate (ISO26262) — slow.arc
  Requirements: 3 total, 3 satisfied, 2 verified
  Constraints: 1 declared, 0 hold

✗ 3 blocker(s):
  [requirements.verification] requirement 'REQ-002': verification cases fail (TC-002)
  [timing.budget] functional_chain 'Reading to burner': sum of function latencies 2200.0 ms exceeds the 2000.0 ms budget
  [constraints.violated] constraint 'Chain within budget' is violated: `sum("FC-HEAT", latency) <= "FC-HEAT".latency_budget` — left side is 2.2 s, right side is 2 s

PRODUCTION GATE: FAIL ✗ (3 blockers)
Error: Compilation error: production gate failed with 3 blocker(s)

The chain now takes 2.2 s. The budget is exceeded, the constraint fails, TC-002 fails with it, and REQ-002 is no longer verified. The model caught a timing problem before any hardware exists.

The complete model

Here is the whole file you built. Every block of it appeared in a step above, in the same order.

heating.arc
model HomeHeating {
  name: "Home heating"
  version: "0.1"
}

requirements {
  req "REQ-001" "Comfort" {
    description: "The system shall keep the living room within 0.5 °C of the set temperature"
    priority: "High"
  }
  req "REQ-002" "Reaction time" {
    description: "The system shall start or stop the burner within 2 s of a reading that crosses the set temperature"
    priority: "Medium"
  }
  req "REQ-003" "Set temperature" {
    description: "The occupant shall be able to change the set temperature on the thermostat"
    priority: "Medium"
  }
}

operational_analysis "Operational Analysis" {
  operational_capability "Live in a warm home" {
    id: "OC-WARM"
    description: "The home stays at the temperature its occupants want"
    involves: ["OA-OCCUPANT", "OA-HOME"]
  }
  actor "Occupant" {
    id: "OA-OCCUPANT"
    description: "A person who lives in the home"
  }
  entity "Home" {
    id: "OA-HOME"
    description: "The house, its rooms and its boiler"
    activity "Take the wished temperature" { id: "OA-WISH" }
    activity "Keep the rooms warm" { id: "OA-WARM" }
  }
  interaction "Wished temperature" { from: "OA-OCCUPANT" to: "OA-WISH" }
  interaction "Temperature to reach" { from: "OA-WISH" to: "OA-WARM" }
}

system_analysis "System Analysis" {
  actor "Occupant" { id: "SA-OCCUPANT" }
  mission "Comfort at home" { id: "MIS-COMFORT" }
  capability "Heat the home" {
    id: "CAP-HEAT"
    mission: "MIS-COMFORT"
    realizes: "OC-WARM"
    involves: ["SA-OCCUPANT", "SF-MEASURE", "SF-SET", "SF-DECIDE", "SF-BURN"]
  }
  function "Measure the room temperature" {
    id: "SF-MEASURE"
    latency: 500 ms
    port out temperature
  }
  function "Take the set temperature" {
    id: "SF-SET"
    port out setpoint
  }
  function "Decide the heating demand" {
    id: "SF-DECIDE"
    latency: 200 ms
    port in temperature
    port in setpoint
    port out demand
  }
  function "Drive the burner" {
    id: "SF-BURN"
    latency: 1 s
    port in demand
  }
  functional_exchange "room temperature" { id: "FE-TEMP" from: "SF-MEASURE.temperature" to: "SF-DECIDE.temperature" }
  functional_exchange "set temperature" { id: "FE-SET" from: "SF-SET.setpoint" to: "SF-DECIDE.setpoint" }
  functional_exchange "heating demand" { id: "FE-DEMAND" from: "SF-DECIDE.demand" to: "SF-BURN.demand" }
  functional_chain "Reading to burner" {
    id: "FC-HEAT"
    involves: ["SF-MEASURE", "SF-DECIDE", "SF-BURN"]
    latency_budget: 2 s
  }
}

logical_architecture "Logical Architecture" {
  component "Thermostat" {
    id: "LC-THERMO"
    function "Sense the temperature" { id: "LF-SENSE" realizes: "SF-MEASURE" latency: 500 ms }
    function "Show and set the temperature" { id: "LF-SET" realizes: "SF-SET" }
    port out readings
  }
  component "Heating controller" {
    id: "LC-CTRL"
    function "Compute the demand" { id: "LF-DECIDE" realizes: "SF-DECIDE" latency: 200 ms }
    port in readings
    port out command
  }
  component "Burner unit" {
    id: "LC-BURNER"
    function "Fire the burner" { id: "LF-BURN" realizes: "SF-BURN" latency: 1 s }
    port in command
  }
  component_exchange "Readings and set point" { id: "CE-READINGS" from_port: "LC-THERMO.readings" to_port: "LC-CTRL.readings" }
  component_exchange "Burner command" { id: "CE-COMMAND" from_port: "LC-CTRL.command" to_port: "LC-BURNER.command" }
}

physical_architecture "Physical Architecture" {
  node "Wall thermostat" {
    id: "PN-WALL"
    processor: "Low-power microcontroller"
    deploys "LC-THERMO"
  }
  node "Boiler control board" {
    id: "PN-BOARD"
    deploys "LC-CTRL"
    deploys "LC-BURNER"
  }
  physical_link "Radio link" {
    id: "PL-RADIO"
    from: "PN-WALL"
    to: "PN-BOARD"
    protocol: "868 MHz radio"
    bandwidth: 20 kbps
  }
}

trace "SF-SET" realizes "OA-WISH"
trace "SF-DECIDE" realizes "OA-WARM"
trace "LC-CTRL" satisfies "REQ-001" { rationale: "Closes the loop between the reading and the set point" }
trace "LC-BURNER" satisfies "REQ-002" { rationale: "Fires the burner at the end of the chain" }
trace "LC-THERMO" satisfies "REQ-003" { rationale: "The set point is changed on the thermostat" }

constraint "Chain within budget" {
  id: "CST-HEAT"
  assert: sum("FC-HEAT", latency) <= "FC-HEAT".latency_budget
}

test_case "TC-001" {
  verifies: ["REQ-001"]
  method: "test"
  description: "Record the room temperature for 24 h in a test house"
}
test_case "TC-002" {
  verifies: ["REQ-002"]
  method: "analysis"
  asserts: ["CST-HEAT"]
  description: "The latencies along the chain Reading to burner fit its budget"
}
test_case "TC-003" {
  verifies: ["REQ-003"]
  method: "demonstration"
  description: "Change the set point on the thermostat and watch the boiler react"
}

You can download it and open it in the playground or in VS Code.

With the compiler installed, draw every view of the model in one self-contained HTML file:

$ arclang diagram heating.arc -f html -o heating.html
✓ oab — 4 nodes, 2 edges
✓ sab — 6 nodes, 3 edges
✓ lab — 7 nodes, 2 edges
✓ pab — 5 nodes, 1 edges
✓ cap — 10 nodes, 9 edges
✓ req — 9 nodes, 6 edges
✓ par — 9 nodes, 4 edges
✓ gv — 10 nodes, 0 edges
✓ iv:Operational Analysis — 2 nodes, 0 edges
✓ afv:System Analysis — 8 nodes, 5 edges
✓ iv:Logical Architecture — 3 nodes, 2 edges
✓ iv:Physical Architecture — 2 nodes, 1 edges
  Output: heating.html

Each line is a view: oab for the operational level, sab for the system, lab for the logical architecture, pab for the physical one, cap for missions and capabilities, req for requirements, par for the constraints, and the views of the SysML v2 standard (gv, iv, afv). Diagrams describes them.

What to do next

  • Modeling with Arcadia explains each level of the method in more depth, with the names of the Capella diagrams, and adds the fifth level, the product breakdown, to this model.
  • Modeling with SysML v2 maps this model to the concepts of the SysML v2 standard and exports it.
  • Getting started installs the compiler on your computer, and the language tour covers the rest of the language: data models, modes and states, scenarios, hazards.

Every ArcLang sample on this page is compiled, and every command is run, with the released compiler when the site is built.