Synthesijerバックエンドでe7UDP/IP IPコアを使う

Pocket

イーツリーズのe7UDP/IP IPコアを使うと,FPGA上のロジック回路と簡単にUDP通信できるようになるので,とっても便利です.

簡単なサンプルが季節のe7UDP/IP IPコアのサンプル(二種)にあるのですが,これをSynthesijerバックエンドを使ったScalaで書き直してみました.

コードはこんな感じ .HDLxxxというクラスはSynthesijerで用意しているHDL生成用のライブラリ群.まずはベタに書いてみたという段階で,HDLxxxというクラスを意識せずにもっとシンプルにかけるようにする,というのは次のステップ.

package upltest

import synthesijer.hdl._
import synthesijer.hdl.expr._
import java.util.EnumSet

class UPLTest {

	val m = new HDLModule("UPLTest", "clk", "reset");
	val uplin = new UPLIn(m, "pI0");
	val uplout = new UPLOut(m, "pO0");

	val ipaddr = m.newPort("pMyIpAddr",    HDLPort.DIR.IN, HDLPrimitiveType.genVectorType(32));
    val port   = m.newPort("pMyPort",       HDLPort.DIR.IN, HDLPrimitiveType.genVectorType(16));
    val server_addr = m.newPort("pServerIpAddr", HDLPort.DIR.IN, HDLPrimitiveType.genVectorType(32));
    val server_port = m.newPort("pServerPort",   HDLPort.DIR.IN, HDLPrimitiveType.genVectorType(16));

    val trigger = m.newPort("trigger",   HDLPort.DIR.IN, HDLPrimitiveType.genBitType());

    // reset
    uplout.req.getSignal().setResetValue(HDLPreDefinedConstant.LOW);
    uplout.en.getSignal().setResetValue(HDLPreDefinedConstant.LOW);
    uplin.ack.getSignal().setResetValue(HDLPreDefinedConstant.LOW);

    val s = m.newSequencer("main");

    // idle
    val idle = s.getIdleState();
    uplout.req.getSignal().setAssign(idle, HDLPreDefinedConstant.LOW);
    uplout.en.getSignal().setAssign(idle, HDLPreDefinedConstant.LOW);

    // s0
    val s0 = s.addSequencerState("S0");
    idle.addStateTransit(m.newExpr(HDLOp.EQ, trigger.getSignal(), HDLPreDefinedConstant.HIGH), s0);
    uplout.req.getSignal().setAssign(s0, 0, HDLPreDefinedConstant.HIGH);

    // s1
    val s1 = s.addSequencerState("S1");
    s0.addStateTransit(m.newExpr(HDLOp.EQ, uplout.ack.getSignal(), HDLPreDefinedConstant.HIGH), s1);
    // s1.0
    uplout.data.getSignal().setAssign(s1, 0, ipaddr.getSignal());
    uplout.en.getSignal().setAssign(s1, 0, HDLPreDefinedConstant.HIGH);
    uplout.req.getSignal().setAssign(s1, 0, HDLPreDefinedConstant.LOW);
    // s1.1
    uplout.data.getSignal().setAssign(s1, 1, server_addr.getSignal());
    // s1.2
    uplout.data.getSignal().setAssign(s1, 2, m.newExpr(HDLOp.CONCAT, port.getSignal(), server_port.getSignal()));
    // s1.3
    uplout.data.getSignal().setAssign(s1, 3, new HDLValue("4", HDLPrimitiveType.genVectorType(32)));
    // s1.4
    uplout.data.getSignal().setAssign(s1, 4, new HDLValue(String.valueOf(0xDEADBEEF), HDLPrimitiveType.genVectorType(32)));
    // s1.5
    uplout.en.getSignal().setAssign(s1, 5, HDLPreDefinedConstant.LOW);
    s1.addStateTransit(idle);
    s1.setMaxConstantDelay(5);

    HDLUtils.generate(m, HDLUtils.VHDL);
    HDLUtils.generate(m, HDLUtils.Verilog);

}

RTL設計では,レジスタとレジスタの値の転送で組み合わせ回路やステートマシンを作り込む必要がありますが,Synthesijerバックエンドを使うと,ステートマシンと演算を直接記述して,同期式順序回路をくみ上げています.

下敷きのHDLコードがあると,HDLとあまり変わりないようにも見えるけど…

トップモジュールの記述では少し楽ができる感じを実感できるかも.

今作ったモジュールとIPコアとして提供されるe7udp/ip,クロック作るDCMのインスタンスを作ってそれぞれ接続するトップモジュールの記述はこんな感じで記述できます.

package upltest

import synthesijer.hdl.expr._
import synthesijer.hdl._

object Main{

  def main(args:Array[String]): Unit = {
    val m0 = new UPLTest();
    val m1 = e7udpip100M_exstick_skel;
    val dcm = dcm_skel.module;

    val m = new HDLModule("top");
    val u0 = m.newModuleInstance(m0.m, "u0");
    val u1 = m.newModuleInstance(m1.module, "u1");
    val u_dcm = m.newModuleInstance(dcm, "u_dcm");

    m1.module.getPorts().foreach( p => Utils.port_export(m, u1, p) );

    Utils.assign(u0, m0.uplin, u1, m1.udprecv0);
    Utils.assign(u1, m1.udpsend0, u0, m0.uplout);

    val ipaddr  = new HDLValue(String.valueOf(0x0a000001), HDLPrimitiveType.genVectorType(32));
    val macaddr = new HDLValue(String.valueOf(0x001b1aff0001L), HDLPrimitiveType.genVectorType(48));
    val netmask = new HDLValue(String.valueOf(0xff000000), HDLPrimitiveType.genVectorType(32));
    val gateway = new HDLValue(String.valueOf(0x0a000001), HDLPrimitiveType.genVectorType(32));
    val server_addr = new HDLValue(String.valueOf(0x0a000003), HDLPrimitiveType.genVectorType(32));
    val server_port0 = new HDLValue(String.valueOf(16384), HDLPrimitiveType.genVectorType(16));
    val server_port1 = new HDLValue(String.valueOf(16385), HDLPrimitiveType.genVectorType(16));

    m.newPort("EPHY_PDW", HDLPort.DIR.OUT, HDLPrimitiveType.genBitType()).getSignal().setAssign(null, HDLPreDefinedConstant.HIGH);

    val reset_n = m.newPort("USER_RESET_N", HDLPort.DIR.IN, HDLPrimitiveType.genBitType());

    Utils.assign(u1, "EPHY_MAC_CLK", u_dcm, "CLK_OUT1"); // expected 25MHz
    Utils.assign(u1, "EPHY_INT_N", HDLPreDefinedConstant.HIGH);
    Utils.assign(u1, "pMyIpAddr", ipaddr);
    Utils.assign(u1, "pMyMacAddr", macaddr);
    Utils.assign(u1, "pMyNetmask", netmask);
    Utils.assign(u1, "pDefaultGateway", gateway);
    Utils.assign(u1, "pTargetIPAddr", server_addr);
    Utils.assign(u1, "pMyUdpPort0", server_port0);
    Utils.assign(u1, "pMyUdpPort1", server_port1);
    Utils.assign(u1, "pUPLGlobalClk", u_dcm, "CLK_OUT2"); // expected 50MHz
    //assign(u1, "Reset_n", reset_n);
    Utils.assign(u1, "Reset_n", HDLPreDefinedConstant.HIGH);

    Utils.assign(u0, "pMyIpAddr", ipaddr);
    Utils.assign(u0, "pMyPort", server_port0);
    Utils.assign(u0, "pServerIpAddr", server_addr);
    Utils.assign(u0, "pServerPort", server_port0);
    val trigger = m.newExpr(HDLOp.NOT, m.newPort("PUSHSW", HDLPort.DIR.IN, HDLPrimitiveType.genBitType()).getSignal());
    Utils.assign(u0, "trigger", trigger);
    Utils.assign(u0, "clk", u_dcm, "CLK_OUT2"); // expected 50MHz
    //assign(u0, "reset", m.newExpr(HDLOp.NOT, reset_n.getSignal()));
    Utils.assign(u0, "reset", HDLPreDefinedConstant.LOW);

    Utils.assign(u_dcm, "CLK_IN1", m.newPort("USER_CLOCK", HDLPort.DIR.IN, HDLPrimitiveType.genBitType()));
    Utils.assign(u_dcm, "RESET", HDLPreDefinedConstant.LOW);

    HDLUtils.generate(m, HDLUtils.VHDL);
    HDLUtils.generate(m, HDLUtils.Verilog);

  }

}

ベタにHDL書くよりは楽..だと思いますが,いかがでしょうか

UPL接続とか,外部に直接引き出す信号の生成とか,単純な記述をメソッド呼び出しで機械的に処理しているので,すっきり書けている…と思うのですが.

少なくともVHDLでべたに書いたコードの行数的が646行だったから,67行と大幅に短縮できて嬉しい…かな.VHDLがcomponent宣言やら何やらで行数が膨らみがちだ,ということで,元の646行が多すぎなだけかもですが.

ちなみにScalaプログラムとして実行するとVHDLとVerilogコードが生成できるので,それをISEでコンパイルすればFPGAで動作させられます.