イーツリーズの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で動作させられます.