Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>IPU AssemblyNew to Visual Studio Code? Get it now.
IPU Assembly

IPU Assembly

yarden carmi

|
7 installs
| (0) | Free
Language support for IPU VLIW assembly (.asm, .asm.j2 templates): highlighting, completion, operand hints, hover docs, navigation, formatting and live assembler errors. In the IPU emulator repository it also adds Run, Debug, Test and Benchmark buttons and a kernel sidebar.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

IPU Assembly — VS Code extension

Editing support for IPU VLIW assembly (.asm, .asm.j2): highlighting, completion, hover, navigation, rename, formatting and assembler diagnostics, plus Run/Debug/Test buttons and an IPU sidebar for the kernel library.

Anything that runs a process needs an IPU checkout: the nearest Bazel workspace above the file holding src/tools/ipu-as-py, with the open folder at, inside, or up to three levels above it; switching checkouts is picked up live. Elsewhere a .asm is only coloured.

Where its knowledge comes from

Knowledge Source When
Token shapes, comments, ; / ;;, labels asm_grammar.lark terminals extension build
Instructions, their slots, slots per word, operands, docs instruction_spec.py extension build
What each register is; CR0/CR1 fixed values ipu_common.registers, ipu_emu.ipu_config extension build
What each operand type accepts (registers, enums, ranges) the assembler's token classes, completion_domain() extension build
Which names ignore case probe programs run through the assembler extension build
Kernels, families and their Bazel targets ipu_app.bzl (:kernel_targets) runtime, from the workspace
Cases, options, operations the kernel registry (:kernel_manifest) runtime, from the workspace
Which kernel handles a computation query --json runtime, from the workspace
Whether a program assembles ipu-as check runtime, from the workspace

None of it is hand-written. Build-time knowledge is bundled as data/isa.json; runtime knowledge comes from the open checkout, so branch-only kernels show up.

Generated, not written

The TextMate grammar in syntaxes/ is generated by ipu_as/gen_vscode.py from two sources, and it is worth being precise about which is which:

  • Structure — comments, ; / ;;, :, the identifier shape — from get_parser().terminals via pattern.to_regexp(). No token text is restated.
  • Vocabulary — which identifiers are instructions or registers — from instruction_spec.py and the register enums.

The vocabulary cannot come from the parser: asm_grammar.lark has a single catch-all TOKEN terminal, so the parser genuinely does not distinguish BKPT from lr0 from my_label. Neither layer is written by hand; both are enumerated.

This matters because the previous, hand-written grammar drifted from the language: it coloured BEQ lr0, cr0, +1;; as valid (commas are a lex error) and failed to recognise end: in BKPT;; end: BKPT;; as a label. Nothing detected either, because nothing compared the grammar to the parser. That is what the agreement test below now does.

Edits reflect automatically on the next build:

Change Effect
A terminal's value in asm_grammar.lark picked up automatically
An instruction added to instruction_spec.py picked up automatically
A new terminal named COMMENT_* / STRING_* / PUNCT_* / WS_* scoped by convention
A new terminal with an unrecognised name build fails, naming the terminal

The last row is the one manual step, and it is deliberate: nothing can guess what colour a brand-new lexical concept should be, so the build stops at the moment of the change rather than quietly leaving it unhighlighted.

Regenerate

bazel run //vscode-ipu-asm:gen_vscode

syntaxes/ipu-asm.tmLanguage.json, language-configuration.json and data/isa.json are build outputs and are not committed.

The agreement test

test/agreement.js tokenizes a corpus twice — once with the Lark parser, once with the exact engine VS Code ships (vscode-textmate + vscode-oniguruma) — and fails on any disagreement about a token's boundaries or role. It runs in .github/workflows/vscode-extension.yml.

The Jinja layer is checked on the raw files: every {# … #}, {% … %} and {{ … }} (even in an assembly comment) must carry its Jinja scope and nothing else may; the regions are cross-checked against Jinja's own lexer.

Kernels aside, test/corpus/edge_cases.asm covers constructs they happen not to use (mid-line labels, labels spelled like mnemonics, every numeric base). It must keep assembling; a change that breaks it is a real grammar regression.

The other tests

Test Checks
test/checker.js which checker runs, when the prebuilt one is stale, and how a process that runs too long is stopped
test/project.js which IPU checkout a file or an open folder belongs to, if any
test/language.js <isa.json> [parser-tokens.json] completion and navigation logic, and agreement with the parser on the corpus
test/runs.js [manifest.json] the run, test, debug, benchmark and query commands, against the real manifest
test/rewrites.js + check_rewrites renaming each label and Jinja symbol, and formatting, leaves the assembled binary unchanged
test/integration/run.js the extension in a real VS Code, then in a parent folder, another project and a pre-manifest checkout

All run in the workflow. The integration test needs @vscode/test-electron (installed without saving) and a display (xvfb-run -a on a server); set IPU_TEST_CHECK_COMMAND to a checker argv to include checker-backed tests.

Publishing and versions

Every push to master that touches the extension or its inputs republishes it. The patch number is the workflow run number, applied on the runner only — the version in package.json is just the major/minor base.

Nothing is written back to the repository. A bumped package.json committed to master would land under this workflow's own paths filter and retrigger it, so the version is set at build time and thrown away.

Marketplace publishing needs a VSCE_PAT repository secret: an Azure DevOps personal access token scoped to Marketplace → Manage, issued for all accessible organizations. Without it the Marketplace step logs a note and skips — a missing secret should not fail an otherwise good build — and the GitHub release below still happens.

Install

Every push to master that changes the extension or the sources its language data is generated from republishes the vscode-latest prerelease:

curl -LO https://github.com/rechefe/ipu-emulator/releases/download/vscode-latest/ipu-asm.vsix
code --install-extension ipu-asm.vsix

Features

  • Hover: an instruction's reference, a register's role, a Jinja name's value.
  • Completion: at an instruction start, the mnemonics that still fit the word (slots filled as CompoundInst._fill_instructions does), each bringing its operands as tab stops named as the instruction spec names them; at an operand, what its type accepts, labels and fitting Jinja names, with an operand hint. A typed space opens it only at an operand, so Enter still ends a line. Templates are read as one rendering ({% if %} branches are alternatives).
  • Values inline: {{ lr_row }} shows the literal value in force there (lr1), as Jinja scopes it.
  • Unused names faded: a label nothing branches to, or a set nothing reads. A name written anywhere else in the file counts as used.
  • Navigation and rename for labels and Jinja set / macro / for names and parameters, scoped as Jinja scopes them. Rename refuses a clash or a change that the assembler reports as a new problem (without the assembler it proceeds on the lexical check and says so). Outline and folding too.
  • Formatting: a label at column 0 on its own line, instructions one indent in, one ;; word per line; nothing but that whitespace changes, and every kernel is already formatted. Typing indents after a label, splits code after a typed ;; and moves a label to column 0 on :.
  • Buttons on a kernel's .asm: Run (bazel run <target> -- --case default), Debug (the same with --config=debug), Test (bazel test <target>) and Benchmark if it has one. They run in a terminal named after the kernel, reused only when its shell is idle.
  • Cases, in the sidebar (a case's pencil, a kernel's +): a form with a field per option, typed as the runner parses it, to run or debug a case with other values, edit a registry case, or add one that starts from a registry case. Saved cases go to ipuAsm.cases; cases.py is never written.
  • IPU sidebar: family, kernel and case with the same actions, and a query that asks the registry which kernel handles an operation. Both read //src/tools/ipu-apps:kernel_manifest, which joins Bazel's targets with the registry's cases and refuses if they disagree. A checkout's manifest is first built when something needs it (the sidebar showing it, a kernel's .asm open, a command), then reloads when kernels or build files change. With several checkouts, each row acts on its own. While it is open, it selects the kernel whose .asm is in the editor.

Settings

Setting Default Purpose
ipuAsm.checkCommand Bazel the checker, as an argv (e.g. a virtualenv's ipu-as check --json --input)
ipuAsm.manifestCommand Bazel the command that prints the kernel manifest
ipuAsm.queryCommand Bazel the query command; --json, the operation and parameters are appended
ipuAsm.bazelFlags none extra flags for Run, Debug, Test and Benchmark, e.g. --define=ipu_proto=1
ipuAsm.cases none cases saved from the form, by kernel: { "tall": { "base": "default", "options": { "rows": 64 } } }
ipuAsm.timeoutSeconds 300 the longest the checker, the manifest or a query may run before it is stopped; 0 for no limit
ipuAsm.wordSeparation space how ;; words are set apart (a label, comment or opening Jinja tag stays with its word): space (an empty CodeLens row; file unchanged), emptyLine (written by formatting), line (colour ipuAsm.wordSeparator) or none
ipuAsm.diagnostics true on/off switches, one per feature: the assembler's errors as you type
ipuAsm.hover true hover on instructions, registers and Jinja names
ipuAsm.completion true completion
ipuAsm.operandHints true operand hints (signature help)
ipuAsm.jinjaValueHints true a set name's literal value after each {{ name }}: {{ lr_row }} lr1
ipuAsm.unusedNames true fading unused labels and set names
ipuAsm.navigation true Go to Definition, Find References and Rename
ipuAsm.outline true the Outline and breadcrumbs
ipuAsm.folding true folding of comments and Jinja blocks
ipuAsm.formatting true Format Document and format on type
ipuAsm.kernelButtons true Run, Debug, Test and Benchmark buttons on a kernel's .asm
ipuAsm.sidebar true the IPU sidebar

Diagnostics

The grammar colours tokens; it cannot say a program is wrong. A TextMate rule assigns scopes to spans and has no notion of what the grammar expects next, and validity is not a lexical property — whether a comma is legal depends on the parser's state, not on the characters. BEQ lr0, cr0, +1;; would be coloured perfectly normally.

So extension.js runs the real assembler and turns its errors into squiggles, covering all three stages:

Example Reported at
template {% for x in %} the template line
parse BEQ lr0, cr0, +1;; the comma
encode BGT lr0 lr1;;, mac.ee r0;; the mnemonic

Templates render in Jinja's sandbox, as the assembler does, because a file is checked as soon as it opens; the extension is also disabled in Restricted Mode. A template that expects harness values should give them a | default(...).

It shells out to ipu-as check --json rather than speaking LSP, which keeps the extension dependency-free — no vscode-languageclient, no bundled node_modules, and the vsix stays a few kilobytes.

By default the extension builds //src/tools/ipu-as-py:ipu-as in the background on a checkout's first check, then runs the resulting binary directly. This avoids Bazel's client startup and workspace lock while typing. Before every check it compares the binary with the assembler sources; if the binary is missing or the sources are newer, that check falls back to bazel run, which rebuilds it, and later checks use the binary directly again.

test/checker.js covers this selection and freshness rule and runs in .github/workflows/vscode-extension.yml.

To bypass Bazel entirely, point ipuAsm.checkCommand at a virtualenv instead:

"ipuAsm.checkCommand": [".venv/bin/ipu-as", "check", "--json", "--input"]

The file path is appended to whatever you configure.

When diagnostics are not running

A broken toolchain must not paint the file red — that would be a diagnostic about the environment masquerading as one about the code. But it must not be silent either: "checker unavailable" and "your code is fine" look identical, and silence is the worse failure.

So a status bar item reports which state you are in — $(check) IPU when diagnostics are live, $(warning) IPU: checks off when they are not. Clicking it, or running IPU Assembly: Why are diagnostics not running?, gives the reason.

The most common reason: ipu-as check is added by the same change as this extension, so a workspace on a branch predating it has no check subcommand and diagnostics cannot run there. Highlighting still works — that is self-contained in the vsix.

ipu-as check is a normal CLI too: it exits 1 on problems and prints file:line:col: error: …, so it works in a pre-commit hook or CI.

Positions inside Jinja templates

The parser only ever sees rendered text, so a byte offset in what it parsed has no general relationship to the file on screen — loops repeat lines, conditionals drop them, whitespace control joins them.

Rather than guess afterwards by matching line content, the checker renders the template a second time with each line tagged by its own line number. The tags are # comments, so they are invisible to the parser, and Jinja carries them through whatever it does to the text — so a diagnostic names the source line exactly, including inside a loop body or below a block of {%- set -%} aliases. The tagged render is used only when it is equivalent to the real one.

Where a construct cannot be tagged safely, or an error carries no position at all, the diagnostic says so with (position approximate) rather than implying byte accuracy.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft