Veriloggenって何だろう(4)Seq

Verilog HDL を生成するための記述を python で行うことで FPGA 回路の生成を楽にする為のライブラリー Veriloggen を使ってみたいと思います。
前回は Always 文で非同期リセットの記述を試してみました。今回は Always の代わりに順序回路を記述するSeqを使ってみたいと思います。

Seq を使った順序回路の記述

前回の Always の代わりに Veriloggen に組み込まれている Seq ライブラリーを使用して、読み易く、そして短いプログラムが書けます。
作り方は Always の時と同じで、Seq でモジュールオブジェクトを作り、それに always の内側で処理する内容を足しこんでいきます。
Seq オブジェクトは、always とその初期化部分を自動で作成してくれます。
主要部分のみ抜き出してみると、次のような記述になります。

    seq = vg.Seq(m, 'seq', clk, rst)
    seq.add( vg.Systask('display', 'LED:%d count:%d', led, count) )
    seq.add( count(count + 1),  cond=(count <  interval-1) )
    seq.add( count(0),          cond=(count == interval-1) )
    seq.add( led(led + 1),      cond=(count == interval-1) )
    seq.make_always()

1行目:モジュール名として 'seq' となるオブジェクト seq を作ります。
3行目:(count < interval-1) の時に count に1を足します。
4行目:(count == interval-1) の時に count を0にします。
5行目:(count == interval-1) の時に led に1 足します。
6行目:always 分を作成します。
これを to_verilog() で Verilog HDL に変換すると次のようになります。

  always @(posedge CLK) begin
    if(RST) begin
      count <= 0;
      LED <= 0;
    end else begin
      $display("LED:%d count:%d", LED, count);
      if(count < INTERVAL - 1) begin
        count <= count + 1;
      end 
      if(count == INTERVAL - 1) begin
        count <= 0;
      end 
      if(count == INTERVAL - 1) begin
        LED <= LED + 1;
      end 
    end

シミュレーションまで含めた記述サンプル

テストベンチ含めた全体のプログラムは次のようになります。

import veriloggen as vg

def mkLed():
    m = vg.Module('blinkled')
    interval = m.Parameter('INTERVAL', 16)
    clk = m.Input('CLK')
    rst = m.Input('RST')
    led = m.OutputReg('LED', 8, initval=0)
    count = m.Reg('count', 32, initval=0)
    
    seq = vg.Seq(m, 'seq', clk, rst)
    seq.add( vg.Systask('display', 'LED:%d count:%d', led, count) )
    seq.add( count(count + 1),  cond=(count <  interval-1)  )
    seq.add( count(0),          cond=(count == interval-1) )
    seq.add( led(led + 1),      cond=(count == interval-1) )
    seq.make_always()
    
    return m

def mkTest():
    m = vg.Module('test')

    # target instance
    led = mkLed()
    
    # copy paras and ports
    params = m.copy_params(led)
    ports = m.copy_sim_ports(led)

    clk = ports['CLK']
    rst = ports['RST']
    
    uut = m.Instance(led, 'uut',
                     params=m.connect_params(led),
                     ports=m.connect_ports(led))
    
    #simulation.setup_waveform(m, uut)
    vg.simulation.setup_waveform(m, uut, m.get_vars())
    vg.simulation.setup_clock(m, clk, hperiod=5)
    init = vg.simulation.setup_reset(m, rst, m.make_reset(), period=100)

    init.add(
        vg.Delay(1000),
        vg.Systask('finish'),
    )

    return m
    
if __name__ == '__main__':
    test = mkTest()
    verilog = test.to_verilog('tmp.v')
    print(verilog)

    sim = vg.simulation.Simulator(test)
    rslt = sim.run()
    print(rslt)

    sim.view_waveform()

これを実行すると次のような tmp.v ファイルが作られ、GTKWave で波形の確認ができます。

module test #
(
  parameter INTERVAL = 16
)
(
);
  reg CLK;
  reg RST;
  wire [8-1:0] LED;

  blinkled
  #(
    .INTERVAL(INTERVAL)
  )
  uut
  (
    .CLK(CLK),
    .RST(RST),
    .LED(LED)
  );

  initial begin
    $dumpfile("uut.vcd");
    $dumpvars(0, uut, CLK, RST, LED);
  end

  initial begin
    CLK = 0;
    forever begin
      #5 CLK = !CLK;
    end
  end

  initial begin
    RST = 0;
    #100;
    RST = 1;
    #100;
    RST = 0;
    #1000;
    $finish;
  end

endmodule

module blinkled #
(
  parameter INTERVAL = 16
)
(
  input CLK,
  input RST,
  output reg [8-1:0] LED
);

  reg [32-1:0] count;

  always @(posedge CLK) begin
    if(RST) begin
      count <= 0;
      LED <= 0;
    end else begin
      $display("LED:%d count:%d", LED, count);
      if(count < INTERVAL - 1) begin
        count <= count + 1;
      end 
      if(count == INTERVAL - 1) begin
        count <= 0;
      end 
      if(count == INTERVAL - 1) begin
        LED <= LED + 1;
      end 
    end
  end

endmodule

f:id:feynman911:20181024001227j:plain

Seq での負論理非同期リセット

Seq を使用した時に負論理非同期リセットを記述するにはどうしたらいいのでしょうか。よくわからないので、make_always() の定義を見てみました。
Spyder を使用している時には make_always() 上で右クリックし '定義へ移動' で Seq.py ファイルの定義箇所が表示されます。

f:id:feynman911:20181024002859j:plain

これを見ると、Posedgeで決め打ちされているようなので、Negedge でリセットが掛かるような always を組むように、Seq.py に make_always_n() を作って使う事にします。
自分が使いたいように自由に組み替えられるのがオープンソースの良いところでしょう。

    def make_always_n(self, reset=(), body=()):
        if self.done:
            #raise ValueError('make_always() has been already called.')
            return

        self.done = True

        part_reset = list(reset) + list(self.make_reset())
        part_body = list(body) + list(self.make_code())

        if not part_reset and not part_body:
            pass
        elif not part_reset or self.rst is None:
            self.m.Always(vtypes.Posedge(self.clk))(
                part_body,
            )
        else:
            self.m.Always(vtypes.Posedge(self.clk),vtypes.Negedge(self.rst))(
                vtypes.If(~self.rst)(
                    part_reset,
                )(
                    part_body,
                ))

Pythonファイルは次のようになります。

import veriloggen as vg

def mkLed():
    m = vg.Module('blinkled')
    interval = m.Parameter('INTERVAL', 16)
    clk = m.Input('CLK')
    rst_n = m.Input('RST_n')
    led = m.OutputReg('LED', 8, initval=0)
    count = m.Reg('count', 32, initval=0)
    
    seq = vg.Seq(m, 'seq', clk, rst_n)
    seq.add( vg.Systask('display', 'LED:%d count:%d', led, count) )
    seq.add( count(count + 1),  cond=(count <  interval-1)  )
    seq.add( count(0),          cond=(count == interval-1) )
    seq.add( led(led + 1),      cond=(count == interval-1) )
    seq.make_always_n()
    
    return m

def mkTest():
    m = vg.Module('test')

    # target instance
    led = mkLed()
    
    # copy paras and ports
    params = m.copy_params(led)
    ports = m.copy_sim_ports(led)

    clk = ports['CLK']
    rst_n = ports['RST_n']
    
    uut = m.Instance(led, 'uut',
                     params=m.connect_params(led),
                     ports=m.connect_ports(led))
    
    #simulation.setup_waveform(m, uut)
    vg.simulation.setup_waveform(m, uut, m.get_vars())
    vg.simulation.setup_clock(m, clk, hperiod=5)
    init = vg.simulation.setup_reset(m, rst_n, m.make_reset(),
                                     period=97, positive=False)

    init.add(
        vg.Delay(1000),
        vg.Systask('finish'),
    )

    return m
    
if __name__ == '__main__':
    test = mkTest()
    verilog = test.to_verilog('tmp.v')
    print(verilog)

    sim = vg.simulation.Simulator(test)
    rslt = sim.run()
    print(rslt)

    sim.view_waveform()

変換された Verilog HDL ファイルは以下の様になります。

module test #
(
  parameter INTERVAL = 16
)
(

);

  reg CLK;
  reg RST_n;
  wire [8-1:0] LED;

  blinkled
  #(
    .INTERVAL(INTERVAL)
  )
  uut
  (
    .CLK(CLK),
    .RST_n(RST_n),
    .LED(LED)
  );

  initial begin
    $dumpfile("uut.vcd");
    $dumpvars(0, uut, CLK, RST_n, LED);
  end

  initial begin
    CLK = 0;
    forever begin
      #5 CLK = !CLK;
    end
  end

  initial begin
    RST_n = 1;
    #97;
    RST_n = 0;
    #97;
    RST_n = 1;
    #1000;
    $finish;
  end

endmodule

module blinkled #
(
  parameter INTERVAL = 16
)
(
  input CLK,
  input RST_n,
  output reg [8-1:0] LED
);

  reg [32-1:0] count;

  always @(posedge CLK or negedge RST_n) begin
    if(~RST_n) begin
      count <= 0;
      LED <= 0;
    end else begin
      $display("LED:%d count:%d", LED, count);
      if(count < INTERVAL - 1) begin
        count <= count + 1;
      end 
      if(count == INTERVAL - 1) begin
        count <= 0;
      end 
      if(count == INTERVAL - 1) begin
        LED <= LED + 1;
      end 
    end
  end

endmodule

シミュレーション結果は下図の様になり、前回と同じように非同期リセットが確認できます。

f:id:feynman911:20181024004418j:plain