Kernel/VM探検隊@つくば No3
筑波大学の COJT とかいう謎授業の自由課題で作った(CPU, OS 自作して単位が貰えて最高!)
CPU を作る中で Veryl という
モダンなハードウェア記述言語に出会った
We've introduced a new semantic analyzer based on intermediate representation to the Veryl compiler.
— dalance (@dalance1982) January 21, 2026
See the blog post below for more details.https://t.co/UhI227hOXe
そこで IR が導入された
将来的には IR を評価してシミュレータも
見えてくるらしい
crates/ 配下に analyzer などの crate があるveryl-analyzer は pub mod ir; を公開していて、Analyzer と Context も public に export している[dependencies]
veryl-analyzer = "0.19.0"
veryl-metadata = "0.19.0"
veryl-parser = "0.19.0"use std::{env, fs}; use veryl_analyzer::{Analyzer, Context, ir::Ir}; use veryl_metadata::Metadata; use veryl_parser::Parser; fn main() { let path = env::args().nth(1).unwrap(); let code = fs::read_to_string(&path).unwrap(); let metadata = Metadata::create_default("prj").unwrap(); let parser = Parser::parse(&code, &path).unwrap(); let analyzer = Analyzer::new(&metadata); let mut context = Context::default(); let mut ir = Ir::default(); let mut errors = vec![]; errors.append(&mut analyzer.analyze_pass1("prj", &parser.veryl)); errors.append(&mut Analyzer::analyze_post_pass1()); errors.append(&mut analyzer.analyze_pass2("prj", &parser.veryl, &mut context, Some(&mut ir))); errors.append(&mut Analyzer::analyze_post_pass2()); if !errors.is_empty() { eprintln!("analyzer errors:"); for e in &errors { eprintln!("{e:?}"); } eprintln!(); } println!("{}", ir); }
use std::{env, fs}; use veryl_analyzer::{Analyzer, Context, ir::Ir}; use veryl_metadata::Metadata; use veryl_parser::Parser; fn main() { let path = env::args().nth(1).unwrap(); let code = fs::read_to_string(&path).unwrap(); let metadata = Metadata::create_default("prj").unwrap(); let parser = Parser::parse(&code, &path).unwrap(); let analyzer = Analyzer::new(&metadata); let mut context = Context::default(); let mut ir = Ir::default(); let mut errors = vec![]; errors.append(&mut analyzer.analyze_pass1("prj", &parser.veryl)); errors.append(&mut Analyzer::analyze_post_pass1()); errors.append(&mut analyzer.analyze_pass2("prj", &parser.veryl, &mut context, Some(&mut ir))); errors.append(&mut Analyzer::analyze_post_pass2()); if !errors.is_empty() { eprintln!("analyzer errors:"); for e in &errors { eprintln!("{e:?}"); } eprintln!(); } println!("{}", ir); }
use std::{env, fs}; use veryl_analyzer::{Analyzer, Context, ir::Ir}; use veryl_metadata::Metadata; use veryl_parser::Parser; fn main() { let path = env::args().nth(1).unwrap(); let code = fs::read_to_string(&path).unwrap(); let metadata = Metadata::create_default("prj").unwrap(); let parser = Parser::parse(&code, &path).unwrap(); let analyzer = Analyzer::new(&metadata); let mut context = Context::default(); let mut ir = Ir::default(); let mut errors = vec![]; errors.append(&mut analyzer.analyze_pass1("prj", &parser.veryl)); errors.append(&mut Analyzer::analyze_post_pass1()); errors.append(&mut analyzer.analyze_pass2("prj", &parser.veryl, &mut context, Some(&mut ir))); errors.append(&mut Analyzer::analyze_post_pass2()); if !errors.is_empty() { eprintln!("analyzer errors:"); for e in &errors { eprintln!("{e:?}"); } eprintln!(); } println!("{}", ir); }
use std::{env, fs}; use veryl_analyzer::{Analyzer, Context, ir::Ir}; use veryl_metadata::Metadata; use veryl_parser::Parser; fn main() { let path = env::args().nth(1).unwrap(); let code = fs::read_to_string(&path).unwrap(); let metadata = Metadata::create_default("prj").unwrap(); let parser = Parser::parse(&code, &path).unwrap(); let analyzer = Analyzer::new(&metadata); let mut context = Context::default(); let mut ir = Ir::default(); let mut errors = vec![]; errors.append(&mut analyzer.analyze_pass1("prj", &parser.veryl)); errors.append(&mut Analyzer::analyze_post_pass1()); errors.append(&mut analyzer.analyze_pass2("prj", &parser.veryl, &mut context, Some(&mut ir))); errors.append(&mut Analyzer::analyze_post_pass2()); if !errors.is_empty() { eprintln!("analyzer errors:"); for e in &errors { eprintln!("{e:?}"); } eprintln!(); } println!("{}", ir); }
use std::{env, fs}; use veryl_analyzer::{Analyzer, Context, ir::Ir}; use veryl_metadata::Metadata; use veryl_parser::Parser; fn main() { let path = env::args().nth(1).unwrap(); let code = fs::read_to_string(&path).unwrap(); let metadata = Metadata::create_default("prj").unwrap(); let parser = Parser::parse(&code, &path).unwrap(); let analyzer = Analyzer::new(&metadata); let mut context = Context::default(); let mut ir = Ir::default(); let mut errors = vec![]; errors.append(&mut analyzer.analyze_pass1("prj", &parser.veryl)); errors.append(&mut Analyzer::analyze_post_pass1()); errors.append(&mut analyzer.analyze_pass2("prj", &parser.veryl, &mut context, Some(&mut ir))); errors.append(&mut Analyzer::analyze_post_pass2()); if !errors.is_empty() { eprintln!("analyzer errors:"); for e in &errors { eprintln!("{e:?}"); } eprintln!(); } println!("{}", ir); }
use std::{env, fs}; use veryl_analyzer::{Analyzer, Context, ir::Ir}; use veryl_metadata::Metadata; use veryl_parser::Parser; fn main() { let path = env::args().nth(1).unwrap(); let code = fs::read_to_string(&path).unwrap(); let metadata = Metadata::create_default("prj").unwrap(); let parser = Parser::parse(&code, &path).unwrap(); let analyzer = Analyzer::new(&metadata); let mut context = Context::default(); let mut ir = Ir::default(); let mut errors = vec![]; errors.append(&mut analyzer.analyze_pass1("prj", &parser.veryl)); errors.append(&mut Analyzer::analyze_post_pass1()); errors.append(&mut analyzer.analyze_pass2("prj", &parser.veryl, &mut context, Some(&mut ir))); errors.append(&mut Analyzer::analyze_post_pass2()); if !errors.is_empty() { eprintln!("analyzer errors:"); for e in &errors { eprintln!("{e:?}"); } eprintln!(); } println!("{}", ir); }
use std::{env, fs}; use veryl_analyzer::{Analyzer, Context, ir::Ir}; use veryl_metadata::Metadata; use veryl_parser::Parser; fn main() { let path = env::args().nth(1).unwrap(); let code = fs::read_to_string(&path).unwrap(); let metadata = Metadata::create_default("prj").unwrap(); let parser = Parser::parse(&code, &path).unwrap(); let analyzer = Analyzer::new(&metadata); let mut context = Context::default(); let mut ir = Ir::default(); let mut errors = vec![]; errors.append(&mut analyzer.analyze_pass1("prj", &parser.veryl)); errors.append(&mut Analyzer::analyze_post_pass1()); errors.append(&mut analyzer.analyze_pass2("prj", &parser.veryl, &mut context, Some(&mut ir))); errors.append(&mut Analyzer::analyze_post_pass2()); if !errors.is_empty() { eprintln!("analyzer errors:"); for e in &errors { eprintln!("{e:?}"); } eprintln!(); } println!("{}", ir); }
input (veryl):
module Counter (
clk: input clock,
rst: input reset,
o : output logic<8>,
) {
always_ff {
if_reset {
o = 0;
} else {
o += 1;
}
}
}
output (ir):
module Counter {
input var0(clk): clock = 1'hx;
input var1(rst): reset = 1'hx;
output var2(o): logic<8> = 8'hxx;
ff (var0, var1) {
if_reset {
var2 = 32'sh00000000;
} else {
var2 = (var2 + 32'sh00000001);
}
}
}
SystemVerilog との共通点を考える
SystemVerilog
Kernel/VM探検隊@つくば No3
CSS で CPU が作られたことは記憶に新しい
i built an entire x86 CPU emulator in CSS (no javascript)
— Rebane (@rebane2001) February 24, 2026
you can write programs in C, compile them to x86 machine code with GCC, and run them inside CSS pic.twitter.com/jU29iLZfbZ
Cascading Style Sheets
Cascading Style Sheets
実は文書のスタイリングが主目的
なので
複雑なロジック部分をVerylで実装することにより
高度なCSSを実現する
input (veryl):
module Top (
a: input i32,
b: input i32,
c: output i32,
) {
var t0: i32;
always_comb {
t0 = a + b;
c = t0 + 5;
}
}
output (css):
:root {
--t0: calc(var(--a) + var(--b));
--c: calc(var(--t0) - 5);
}
https://github.com/Ryoga-exe/veryl-css
always_comb / alawys_ff / if_reset / if / case / varを CSS にコンパイル可能
module seg7 (
d0 : input u8,
d1 : input u8,
d2 : input u8,
d3 : input u8,
out: output u8,
s0 : output u8,
s1 : output u8,
s2 : output u8,
s3 : output u8,
s4 : output u8,
s5 : output u8,
s6 : output u8,
) {
var data: u8;
var seg: u8;
always_comb {
data = 8 * d0 + 4 * d1 + 2 * d2 + 1 * d3;
out = data;
case data {
0 : seg = 8'b0111111;
1 : seg = 8'b0000110;
2 : seg = 8'b1011011;
3 : seg = 8'b1001111;
4 : seg = 8'b1100110;
5 : seg = 8'b1101101;
6 : seg = 8'b1111101;
7 : seg = 8'b0000111;
8 : seg = 8'b1111111;
9 : seg = 8'b1101111;
default: seg = 0;
}
s0 = seg[0];
s1 = seg[1];
s2 = seg[2];
s3 = seg[3];
s4 = seg[4];
s5 = seg[5];
s6 = seg[6];
}
}/* generated by veryl-css */
@function --veryl-eq(--a <number>, --b <number>) returns <integer> {
result: clamp(0, calc(1 - abs(sign(calc(var(--a) - var(--b))))), 1);
}
@property --veryl-cond-0 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --veryl-cond-1 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --veryl-cond-2 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --veryl-cond-3 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --veryl-cond-4 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --veryl-cond-5 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --veryl-cond-6 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --veryl-cond-7 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --veryl-cond-8 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --veryl-cond-9 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --d0 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --d1 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --d2 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --d3 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --out {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --s0 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --s1 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --s2 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --s3 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --s4 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --s5 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --s6 {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --seg7-data {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --seg7-seg {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
:root {
--seg7-data: calc((calc((calc((calc((8) * (var(--d0)))) +
(calc((4) * (var(--d1)))))) +
(calc((2) * (var(--d2)))))) +
(calc((1) * (var(--d3)))));
--out: var(--seg7-data);
--veryl-cond-0: --veryl-eq(var(--seg7-data), 0);
--veryl-cond-1: --veryl-eq(var(--seg7-data), 1);
--veryl-cond-2: --veryl-eq(var(--seg7-data), 2);
--veryl-cond-3: --veryl-eq(var(--seg7-data), 3);
--veryl-cond-4: --veryl-eq(var(--seg7-data), 4);
--veryl-cond-5: --veryl-eq(var(--seg7-data), 5);
--veryl-cond-6: --veryl-eq(var(--seg7-data), 6);
--veryl-cond-7: --veryl-eq(var(--seg7-data), 7);
--veryl-cond-8: --veryl-eq(var(--seg7-data), 8);
--veryl-cond-9: --veryl-eq(var(--seg7-data), 9);
--seg7-seg: if(style(--veryl-cond-0: 1): 63;
else: if(style(--veryl-cond-1: 1): 6;
else: if(style(--veryl-cond-2: 1): 91;
else: if(style(--veryl-cond-3: 1): 79;
else: if(style(--veryl-cond-4: 1): 102;
else: if(style(--veryl-cond-5: 1): 109;
else: if(style(--veryl-cond-6: 1): 125;
else: if(style(--veryl-cond-7: 1): 7;
else: if(style(--veryl-cond-8: 1): 127;
else: if(style(--veryl-cond-9: 1): 111;
else: 0))))))))));
--s0: mod(var(--seg7-seg), 2);
--s1: mod(round(down, var(--seg7-seg) / 2), 2);
--s2: mod(round(down, var(--seg7-seg) / 4), 2);
--s3: mod(round(down, var(--seg7-seg) / 8), 2);
--s4: mod(round(down, var(--seg7-seg) / 16), 2);
--s5: mod(round(down, var(--seg7-seg) / 32), 2);
--s6: mod(round(down, var(--seg7-seg) / 64), 2);
}
module Counter (
clk: input clock,
rst: input reset,
o : output i32,
) {
always_ff {
if_reset {
o = 0;
} else {
o = o + 1;
}
}
}/* generated by veryl-css */
@property --o {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
@property --rst {
syntax: "<integer>";
inherits: true;
initial-value: 0;
}
body {
--o: var(--Counter-o-hoist, 0);
--Counter-o-next: if(style(--rst: 1): 0; else: calc((var(--o)) + (1)));
}
@keyframes hoist {
0%, 100% {
--Counter-o-hoist: var(--Counter-o-captured, 0);
}
}
@keyframes capture {
0%, 100% {
--Counter-o-captured: var(--Counter-o-next);
}
}
↓ veryl-parser + veryl-analyzer
↓ codegen.rs
本質:emit_module() 関数
if() の取り扱い:eval_if_expr()
CSS には if() があるが、
単純な等値比較しか扱えない
if(style(--foobar: 1): 10; else: 20;)
↑ 時代を先取りしすぎているため、うまくシンタックスハイライトが効かない
ここでおもむろに CSS WG の CSS Values and Units Module Level 4 を見てみる
roundrem, modpowsqrtlogexpabsmin / max / clampsignsin, cos, tan, asin, .../* a < b なら 1、それ以外は 0 */
@function --lt(--a <number>, --b <number>) returns <integer> {
result: clamp(0, sign(calc(var(--b) - var(--a))), 1);
}
/* a > b なら 1、それ以外は 0 */
@function --gt(--a <number>, --b <number>) returns <integer> {
result: clamp(0, sign(calc(var(--a) - var(--b))), 1);
}→ これに入れて 1 かどうかを比較すればよい
https://github.com/Ryoga-exe/veryl-css
