n회 cycle을 단순지연시키는 모듈을 n을 통해 paramaterize하고,
for문으로 통해 code generate를 하면 아래와 같이 elaborate된다.
class DelayNCycles(n: Int) extends Module {
val io = IO(new Bundle {
val in = Input(Bool())
val out = Output(Bool())
})
var lastConn = io.in
for (i <- 0 until n){
println(s"${lastConn} = RegNext($lastConn)")
lastConn = RegNext(lastConn)
}
io.out := lastConn
}
println(getVerilog(new DelayNCycles(3)))
Random Constraint Verification에서는 못쓰지만,
Manually하게 모든 경우의 수를 조사할때는 충분히 가치가 있다.
test(new CombLogic) { dut =>
for (a <- Seq(true, false)) {
for (b <- Seq(true, false)) {
for (c <- Seq(true, false)) {
dut.io.a.poke(a.B)
dut.io.b.poke(b.B)
dut.io.c.poke(c.B)
println(s"$a, $b, $c")
val expected = (a && b) || !c
dut.io.out.expect(expected.B)
// dut.clock.step()
}
}
}
}
실행결과
즉,
class DivByXTable(x: Int) extends Module {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out = Output(Bool())
})
var results = Seq[Bool]()
for (i <- 0 to 15) {
results = results :+ (i % x == 0).B
}
val table = VecInit(results)
io.out := table(io.in)
}
class RegFile(nRead: Int) extends Module {
val io = IO(new Bundle {
val r0addr = Input(UInt(5.W))
val r1addr = Input(UInt(5.W))
val w0addr = Input(UInt(5.W))
val w0en = Input(Bool())
val w0data = Input(UInt(64.W))
val r0out = Output(UInt(64.W))
val r1out = Output(UInt(64.W))
})
// val regs = Mem(32, UInt(64.W))
val regs = Reg(Vec(32, UInt(64.W)))
io.r0out := regs(io.r0addr)
io.r1out := regs(io.r1addr)
when(io.w0en) {
regs(io.w0addr) := io.w0data
}
}
5비트 주소체계를 가지는 각 64비트짜리 Register File 32개를 선언하고자 한다면, 위처럼 쓰면 된다.
elab결과,
module RegFile(
input clock,
input reset,
input [4:0] io_r0addr,
input [4:0] io_r1addr,
input [4:0] io_w0addr,
input io_w0en,
input [63:0] io_w0data,
output [63:0] io_r0out,
output [63:0] io_r1out
);
`ifdef RANDOMIZE_REG_INIT
reg [63:0] _RAND_0;
reg [63:0] _RAND_1;
reg [63:0] _RAND_2;
reg [63:0] _RAND_3;
reg [63:0] _RAND_4;
reg [63:0] _RAND_5;
reg [63:0] _RAND_6;
reg [63:0] _RAND_7;
reg [63:0] _RAND_8;
reg [63:0] _RAND_9;
reg [63:0] _RAND_10;
...
reg [63:0] _RAND_27;
reg [63:0] _RAND_28;
reg [63:0] _RAND_29;
reg [63:0] _RAND_30;
reg [63:0] _RAND_31;
`endif // RANDOMIZE_REG_INIT
reg [63:0] regs_0; // @[cmd32.sc 12:19]
reg [63:0] regs_1; // @[cmd32.sc 12:19]
reg [63:0] regs_2; // @[cmd32.sc 12:19]
reg [63:0] regs_3; // @[cmd32.sc 12:19]
reg [63:0] regs_4; // @[cmd32.sc 12:19]
reg [63:0] regs_5; // @[cmd32.sc 12:19]
reg [63:0] regs_6; // @[cmd32.sc 12:19]
...
reg [63:0] regs_20; // @[cmd32.sc 12:19]
reg [63:0] regs_21; // @[cmd32.sc 12:19]
reg [63:0] regs_22; // @[cmd32.sc 12:19]
reg [63:0] regs_23; // @[cmd32.sc 12:19]
reg [63:0] regs_24; // @[cmd32.sc 12:19]
reg [63:0] regs_25; // @[cmd32.sc 12:19]
reg [63:0] regs_26; // @[cmd32.sc 12:19]
reg [63:0] regs_27; // @[cmd32.sc 12:19]
reg [63:0] regs_28; // @[cmd32.sc 12:19]
reg [63:0] regs_29; // @[cmd32.sc 12:19]
reg [63:0] regs_30; // @[cmd32.sc 12:19]
reg [63:0] regs_31; // @[cmd32.sc 12:19]
wire [63:0] _GEN_1 = 5'h1 == io_r0addr ? regs_1 : regs_0; // @[cmd32.sc 13:14 cmd32.sc 13:14]
wire [63:0] _GEN_2 = 5'h2 == io_r0addr ? regs_2 : _GEN_1; // @[cmd32.sc 13:14 cmd32.sc 13:14]
wire [63:0] _GEN_3 = 5'h3 == io_r0addr ? regs_3 : _GEN_2; // @[cmd32.sc 13:14 cmd32.sc 13:14]
wire [63:0] _GEN_4 = 5'h4 == io_r0addr ? regs_4 : _GEN_3; // @[cmd32.sc 13:14 cmd32.sc 13:14]
wire [63:0] _GEN_5 = 5'h5 == io_r0addr ? regs_5 : _GEN_4; // @[cmd32.sc 13:14 cmd32.sc 13:14]
wire [63:0] _GEN_6 = 5'h6 == io_r0addr ? regs_6 : _GEN_5; // @[cmd32.sc 13:14 cmd32.sc 13:14]
wire [63:0] _GEN_7 = 5'h7 == io_r0addr ? regs_7 : _GEN_6; // @[cmd32.sc 13:14 cmd32.sc 13:14]
wire [63:0] _GEN_8 = 5'h8 == io_r0addr ? regs_8 : _GEN_7; // @[cmd32.sc 13:14 cmd32.sc 13:14]
wire [63:0] _GEN_9 = 5'h9 == io_r0addr ? regs_9 : _GEN_8; // @[cmd32.sc 13:14 cmd32.sc 13:14]
...
wire [63:0] _GEN_59 = 5'h1b == io_r1addr ? regs_27 : _GEN_58; // @[cmd32.sc 14:14 cmd32.sc 14:14]
wire [63:0] _GEN_60 = 5'h1c == io_r1addr ? regs_28 : _GEN_59; // @[cmd32.sc 14:14 cmd32.sc 14:14]
wire [63:0] _GEN_61 = 5'h1d == io_r1addr ? regs_29 : _GEN_60; // @[cmd32.sc 14:14 cmd32.sc 14:14]
wire [63:0] _GEN_62 = 5'h1e == io_r1addr ? regs_30 : _GEN_61; // @[cmd32.sc 14:14 cmd32.sc 14:14]
assign io_r0out = 5'h1f == io_r0addr ? regs_31 : _GEN_30; // @[cmd32.sc 13:14 cmd32.sc 13:14]
assign io_r1out = 5'h1f == io_r1addr ? regs_31 : _GEN_62; // @[cmd32.sc 14:14 cmd32.sc 14:14]
always @(posedge clock) begin
if (io_w0en) begin // @[cmd32.sc 15:19]
if (5'h0 == io_w0addr) begin // @[cmd32.sc 16:25]
regs_0 <= io_w0data; // @[cmd32.sc 16:25]
end
end
if (io_w0en) begin // @[cmd32.sc 15:19]
if (5'h1 == io_w0addr) begin // @[cmd32.sc 16:25]
regs_1 <= io_w0data; // @[cmd32.sc 16:25]
end
end
if (io_w0en) begin // @[cmd32.sc 15:19]
if (5'h2 == io_w0addr) begin // @[cmd32.sc 16:25]
regs_2 <= io_w0data; // @[cmd32.sc 16:25]
end
end
if (io_w0en) begin // @[cmd32.sc 15:19]
if (5'h3 == io_w0addr) begin // @[cmd32.sc 16:25]
regs_3 <= io_w0data; // @[cmd32.sc 16:25]
end
end
if (io_w0en) begin // @[cmd32.sc 15:19]
if (5'h4 == io_w0addr) begin // @[cmd32.sc 16:25]
regs_4 <= io_w0data; // @[cmd32.sc 16:25]
end
end
...
if (io_w0en) begin // @[cmd32.sc 15:19]
if (5'hb == io_w0addr) begin // @[cmd32.sc 16:25]
regs_11 <= io_w0data; // @[cmd32.sc 16:25]
end
end
if (io_w0en) begin // @[cmd32.sc 1
....
처럼 구현된다.
이 부분은 Verilog보다 훨씬 깔끔하다.
class RegFile(nRead: Int) extends Module {
val io = IO(new Bundle {
val raddr = Input(Vec(nRead, UInt(5.W)))
val w0addr = Input(UInt(5.W))
val w0en = Input(Bool())
val w0data = Input(UInt(64.W))
val rout = Output(Vec(nRead, UInt(64.W)))
})
val regs = Mem(32, UInt(64.W))
for (i <- 0 until nRead)
io.rout(i) := regs(io.raddr(i))
when(io.w0en) {
regs(io.w0addr) := io.w0data
}
}
elaborate 결과,
module RegFile(
input clock,
input reset,
input [4:0] io_raddr_0,
input [4:0] io_raddr_1,
input [4:0] io_raddr_2,
input [4:0] io_raddr_3,
input [4:0] io_w0addr,
input io_w0en,
input [63:0] io_w0data,
output [63:0] io_rout_0,
output [63:0] io_rout_1,
output [63:0] io_rout_2,
output [63:0] io_rout_3
);
`ifdef RANDOMIZE_MEM_INIT
reg [63:0] _RAND_0;
`endif // RANDOMIZE_MEM_INIT
reg [63:0] regs [0:31]; // @[cmd40.sc 9:19]
wire [63:0] regs_MPORT_data; // @[cmd40.sc 9:19]
wire [4:0] regs_MPORT_addr; // @[cmd40.sc 9:19]
wire [63:0] regs_MPORT_1_data; // @[cmd40.sc 9:19]
wire [4:0] regs_MPORT_1_addr; // @[cmd40.sc 9:19]
wire [63:0] regs_MPORT_2_data; // @[cmd40.sc 9:19]
wire [4:0] regs_MPORT_2_addr; // @[cmd40.sc 9:19]
wire [63:0] regs_MPORT_3_data; // @[cmd40.sc 9:19]
wire [4:0] regs_MPORT_3_addr; // @[cmd40.sc 9:19]
wire [63:0] regs_MPORT_4_data; // @[cmd40.sc 9:19]
wire [4:0] regs_MPORT_4_addr; // @[cmd40.sc 9:19]
wire regs_MPORT_4_mask; // @[cmd40.sc 9:19]
wire regs_MPORT_4_en; // @[cmd40.sc 9:19]
assign regs_MPORT_addr = io_raddr_0;
assign regs_MPORT_data = regs[regs_MPORT_addr]; // @[cmd40.sc 9:19]
assign regs_MPORT_1_addr = io_raddr_1;
assign regs_MPORT_1_
...
로, 아주 쉽게 parameterize됨을 확인할 수 있다.
Verilog의 경우, input port 갯수자체는 paramaterize화가 까다로운 것으로 알고있다(packed array로 input을 선언하면 되겠다만...처리하기 툭하면 genvar쓰는 등, 여러모로 귀찮을 것이다)