diff --git a/docs/Languages.md b/docs/Languages.md index aa9bf81989..5e72a98acf 100644 --- a/docs/Languages.md +++ b/docs/Languages.md @@ -215,6 +215,7 @@ - VCL: Varnish Configuration Language (`vcl`) - Velocity (`velocity`) - Verilog and System Verilog (`verilog`) +- Veryl (`veryl`) - VHDL 2008 (`vhdl`) - VimL (`viml`) - Visual Basic (`vb`) diff --git a/lib/rouge/demos/veryl b/lib/rouge/demos/veryl new file mode 100644 index 0000000000..8b320f0a8b --- /dev/null +++ b/lib/rouge/demos/veryl @@ -0,0 +1,24 @@ +/// Module A +module ModuleA #( + param ParamA: u32 = 10, +) ( + i_clk : input clock , + i_rst : input reset , + i_sel : input logic , + i_data: input logic [2], + o_data: output logic , +) { + var r_data: logic; + + always_ff { + if_reset { + r_data = 0; + } else if i_sel { + r_data = i_data[0]; + } else { + r_data = i_data[1]; + } + } + + assign o_data = r_data; +} diff --git a/lib/rouge/lexers/veryl.rb b/lib/rouge/lexers/veryl.rb new file mode 100644 index 0000000000..71085cab5e --- /dev/null +++ b/lib/rouge/lexers/veryl.rb @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +module Rouge + module Lexers + class Veryl < RegexLexer + title "Veryl" + desc "The Veryl hardware description language (https://veryl-lang.org)" + tag 'veryl' + filenames '*.veryl' + mimetypes 'text/x-veryl' + + # Characters + + WHITE_SPACE = /\s+/ + NEWLINE = /\n/ + + # Comments + + LINE_COMMENT = /\/\/(?:(?!#{NEWLINE}).)*/ + GENERAL_COMMENT = /\/\*(?:(?!\*\/).)*\*\//m + COMMENT = /#{LINE_COMMENT}|#{GENERAL_COMMENT}/ + + # Numeric literals + + EXPONENT = /[0-9]+(?:_[0-9]+)*\.[0-9]+(?:_[0-9]+)*[eE][+-]?[0-9]+(?:_[0-9]+)*/ + FIXED_POINT = /[0-9]+(?:_[0-9]+)*\.[0-9]+(?:_[0-9]+)*/ + BASED = /(?:[0-9]+(?:_[0-9]+)*)?'s?[bodh][0-9a-fA-FxzXZ]+(?:_[0-9a-fA-FxzXZ]+)*/ + ALL_BIT = /(?:[0-9]+(?:_[0-9]+)*)?'[01xzXZ]/ + BASE_LESS = /[0-9]+(?:_[0-9]+)*/ + + # Operators and delimiters + + OPERATOR = / -: | -> | \+: | \+= | -= + | \*= | \/= | %= | &= | \|= + | \^= | <<= | >>= |<<<= |>>>= + | <> | \*\* | \/ | \| | % + | \+ | - | <<< | >>> | << + | >> | <= | >= | <: | >: + | === | ==\? | \!== | \!=\? | == + | \!= | && | \|\| | & | \^~ + | \^ | ~\^ | \| | ~& | ~\| + | \! | ~ + /x + + SEPARATOR = / ::< | :: | : | , | \.\.= + | \.\. | \. | = | \# | < + | \? | ' | '\{ | \{ | \[ + | \( | > | \} | \] | \) + | ; | \* + /x + + # Identifiers + + DOLLAR_IDENTIFIER = /\$[a-zA-Z_][0-9a-zA-Z_$]*/ + IDENTIFIER = /(?:r#)?[a-zA-Z_][0-9a-zA-Z_$]*/ + + # Keywords + + def self.keywords + @keywords ||= Set.new %w( + embed enum function include interface modport module package proto pub struct union unsafe + alias always_comb always_ff assign as bind connect const final import initial inst let param return break type var + converse inout input output same + case default else if_reset if inside outside switch + for in repeat rev step + ) + end + + def self.keywords_type + @keywords_type ||= Set.new %w( + bit bool clock clock_posedge clock_negedge f32 f64 i8 i16 i32 i64 logic reset reset_async_high reset_async_low reset_sync_high reset_sync_low signed string tri u8 u16 u32 u64 + ) + end + + state :root do + rule(COMMENT , Comment ) + rule(EXPONENT , Num::Float ) + rule(FIXED_POINT , Num::Float ) + rule(BASED , Num::Integer ) + rule(ALL_BIT , Num::Integer ) + rule(BASE_LESS , Num::Integer ) + rule(OPERATOR , Operator ) + rule(SEPARATOR , Punctuation ) + rule(DOLLAR_IDENTIFIER, Name ) + rule(WHITE_SPACE , Text ) + rule(/"/ , Str::Double, :string) + + rule IDENTIFIER do |m| + name = m[0] + + if self.class.keywords.include? name + token Keyword + elsif self.class.keywords_type.include? name + token Keyword::Type + else + token Name + end + end + end + + state :string do + rule(/[^\\"]+/, Str::Double ) + rule(/\\./ , Str::Escape ) + rule(/"/ , Str::Double, :pop!) + end + end + end +end diff --git a/spec/lexers/veryl_spec.rb b/spec/lexers/veryl_spec.rb new file mode 100644 index 0000000000..c05e38177e --- /dev/null +++ b/spec/lexers/veryl_spec.rb @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- # +# frozen_string_literal: true + +describe Rouge::Lexers::Veryl do + let(:subject) { Rouge::Lexers::Veryl.new } + + describe 'guessing' do + include Support::Guessing + + it 'guesses by filename' do + assert_guess :filename => 'foo.veryl' + end + + it 'guesses by mimetype' do + assert_guess :mimetype => 'text/x-veryl' + end + end +end diff --git a/spec/visual/samples/veryl b/spec/visual/samples/veryl new file mode 100644 index 0000000000..d94a448315 --- /dev/null +++ b/spec/visual/samples/veryl @@ -0,0 +1,155 @@ +// The following code is from https://github.com/veryl-lang/doc/tree/main/book/src/04_code_examples +// Copyright (c) 2024 Naoye Hatta +// Provided under Apache-2.0 or MIT license + +// module definition +module ModuleA #( + param ParamA: u32 = 10, + const ParamB: u32 = 10, // trailing comma is allowed +) ( + i_clk : input clock , // `clock` is a special type for clock + i_rst : input reset , // `reset` is a special type for reset + i_sel : input logic , + i_data: input logic [2], // `[]` means unpacked array in SystemVerilog + o_data: output logic , // `<>` means packed array in SystemVerilog +) { + // const parameter declaration + // `param` is not allowed in module + const ParamC: u32 = 10; + + // variable declaration + var r_data0: logic; + var r_data1: logic; + var r_data2: logic; + + // value binding + let _w_data2: logic = i_data[0]; + + // always_ff statement with reset + // `always_ff` can take a mandatory clock and a optional reset + // `if_reset` means `if (i_rst)`. This conceals reset porality + // `()` of `if` is not required + // `=` in `always_ff` is non-blocking assignment + always_ff (i_clk, i_rst) { + if_reset { + r_data0 = 0; + } else if i_sel { + r_data0 = i_data[0]; + } else { + r_data0 = i_data[1]; + } + } + + + // always_ff statement without reset + always_ff (i_clk) { + r_data1 = r_data0; + } + + // clock and reset can be omitted + // if there is a single clock and reset in the module + always_ff { + r_data2 = r_data1; + } + + assign o_data = r_data1; +} + +module ModuleA #( + param ParamA: u32 = 10, +) ( + i_clk : input clock , + i_rst : input reset , + i_data: input logic, + o_data: output logic, +) { + var r_data1: logic; + var r_data2: logic; + + assign r_data1 = i_data + 1; + assign o_data = r_data2 + 2; + + // instance declaration + // `inst` keyword starts instance declaration + // port connnection can be specified by `()` + // each port connection is `[port_name]:[variable]` + // `[port_name]` means `[port_name]:[port_name]` + inst u_module_b: ModuleB ( + i_clk , + i_data: r_data1, + o_data: r_data2, + ); + + // instance declaration with parameter override + // notation of parameter connection is the same as port + inst u_module_c: ModuleC #( ParamA, ParamB: 10 ); +} + +module ModuleB #( + param ParamA: u32 = 10, +) ( + i_clk : input clock , + i_data: input logic, + o_data: output logic, +) { + assign o_data = 1; +} + +module ModuleC #( + param ParamA: u32 = 10, + param ParamB: u32 = 10, +) () {} + +// interface definition +interface InterfaceA #( + param ParamA: u32 = 1, + param ParamB: u32 = 1, +) { + const ParamC: u32 = 1; + + var a: logic; + var b: logic; + var c: logic; + + // modport definition + modport master { + a: input , + b: input , + c: output, + } + + modport slave { + a: input , + b: input , + c: output, + } +} + +module ModuleA ( + i_clk: input clock, + i_rst: input reset, + // port declaration by modport + intf_a_mst: modport InterfaceA::master, + intf_a_slv: modport InterfaceA::slave , +) { + // interface instantiation + inst u_intf_a: InterfaceA [10]; +} + +// package definition +package PackageA { + const ParamA: u32 = 1; + const ParamB: u32 = 1; + + function FuncA ( + a: input logic, + ) -> logic { + return a + 1; + + } +} + +module ModuleA { + let a : logic<10> = PackageA::ParamA; + let _b: logic<10> = PackageA::FuncA(a); +}