放置気味だったSystemCの話を回収。前回は合成可能なVerilogをSystemCで書き直したけれど、そもそも本気で石にするシステムをSystemCで書く気なんて全くない。単にVerilatorを使ってVerilogの検証環境を構築したかっただけなのだ。
…と言うわけで、適当な回路のテストベンチを書いてみる。まず、テスト対象の回路はこのローパスフィルタ(lpf.v)。
// low pass filter
module lpf (
input clk,
input xrst,
input i_vld,
input [7:0] i_d,
output o_vld,
output [7:0] o_d
);
reg [7:0] r_d_1z;
reg [7:0] r_d_2z;
reg [7:0] r_d_3z;
reg r_vld_1z;
reg r_vld_2z;
reg r_vld_3z;
reg [7:0] r_od;
reg r_ovld;
wire [7:0] w_d0;
wire [7:0] w_d1;
wire [7:0] w_d2;
wire [9:0] w_sum;
wire [8:0] w_round;
wire w_ovld;
// valid
always @(posedge clk or negedge xrst) begin
if (!xrst) begin
r_vld_1z <= 1'b0;
r_vld_2z <= 1'b0;
r_vld_3z <= 1'b0;
end else begin
r_vld_1z <= i_vld;
r_vld_2z <= r_vld_1z;
r_vld_3z <= r_vld_2z;
end
end
// taps
always @(posedge clk or negedge xrst) begin
if (!xrst) begin
r_d_1z[7:0] <= 8'd0;
r_d_2z[7:0] <= 8'd0;
r_d_3z[7:0] <= 8'd0;
end else begin
r_d_1z[7:0] <= i_d[7:0];
r_d_2z[7:0] <= r_d_1z[7:0];
r_d_3z[7:0] <= r_d_2z[7:0];
end
end
assign w_d0[7:0] = (r_vld_1z)? r_d_1z[7:0] : w_d1[7:0];
assign w_d1[7:0] = (r_vld_2z)? r_d_2z[7:0] : 8'd0;
assign w_d2[7:0] = (r_vld_3z)? r_d_3z[7:0] : w_d1[7:0];
// filter
assign w_sum[9:0] = {2'b00, w_d0[7:0]} +
{1'b0, w_d1[7:0], 1'b0} +
{2'b00, w_d2[7:0]};
assign w_round[8:0] = w_sum[9:1] + {8'd0, w_sum[1]};
// output
always @(posedge clk or negedge xrst) begin
if (!xrst)
r_od[7:0] <= 8'd0;
else
r_od[7:0] <= w_round[8:1];
end
assign o_d[7:0] = r_od[7:0];
assign o_vld = r_vld_3z;
endmodule
で、そのテストベンチがこれ(test_lpf.cpp)。ちと長いけど、そのままペーストしてしまおう。
#include "Vlpf.h"
#include "verilated_vcd_sc.h"
#include <cstdlib>
#include <queue>
#include <iostream>
#define DUMP_VCD
// source
SC_MODULE(Source) {
static const int N = 10000;
sc_in<bool> clk, xrst;
sc_out<bool> vld;
sc_out<uint32_t> d;
sc_event done;
SC_CTOR(Source) : done() {
SC_CTHREAD(proc, clk.pos());
async_reset_signal_is(xrst, false);
}
void proc() {
int n = N;
while (n > 0) {
vld.write(0);
wait(10);
for (int i = 0; i < 100; i++, n--) {
vld.write(1);
int v = rand() & 0x3f;
if (i % 10 == 5) v += 100;
d.write(v & 0xff);
wait();
}
}
vld.write(0);
done.notify();
}
};
// sink
SC_MODULE(Sink) {
sc_in<bool> clk, xrst;
sc_in<bool> i_dut_vld, i_exp_vld;
sc_in<uint32_t> i_dut_d, i_exp_d;
int n_errors;
SC_CTOR(Sink) : n_errors(0) {
SC_CTHREAD(proc, clk.pos());
async_reset_signal_is(xrst, false);
}
void proc() {
std::queue<uint32_t> dut_d = std::queue<uint32_t>();
std::queue<uint32_t> exp_d = std::queue<uint32_t>();
int cnt = 0;
wait();
while (true) {
if (i_dut_vld.read()) dut_d.push(i_dut_d.read());
if (i_exp_vld.read()) exp_d.push(i_exp_d.read());
if (!dut_d.empty() && !exp_d.empty()) {
uint32_t d = dut_d.front();
uint32_t e = exp_d.front();
const char *jdg;
if (d == e) {
jdg = "OK";
} else {
jdg = "NG";
n_errors++;
}
std::cout << '[' << cnt++ << "] : " << jdg
<< " dut = " << d << ", exp = " << e << std::endl;
dut_d.pop();
exp_d.pop();
}
wait();
}
}
};
// model
SC_MODULE(Model) {
sc_in<bool> clk, xrst;
sc_in<bool> i_vld;
sc_in<uint32_t> i_d;
sc_out<bool> o_vld;
sc_out<uint32_t> o_d;
SC_CTOR(Model) {
SC_CTHREAD(proc, clk.pos());
async_reset_signal_is(xrst, false);
}
void proc() {
uint32_t d[3] = {0, 0, 0};
uint32_t sum;
bool vld[3] = {false, false, false};
wait();
while (true) {
d[2] = d[1];
d[1] = d[0];
d[0] = i_d.read() & 0xff;
vld[2] = vld[1];
vld[1] = vld[0];
vld[0] = i_vld.read();
sum = d[1] * 2;
sum += (vld[2])? d[2] : d[1];
sum += (vld[0])? d[0] : d[1];
o_vld.write(vld[1]);
o_d.write((sum + 2) / 4);
wait();
}
}
};
// testbench
SC_MODULE(Testbench) {
sc_in<bool> clk;
sc_signal<bool> xrst, i_vld, o_dut_vld, o_exp_vld;
sc_signal<uint32_t> i_d, o_dut_d, o_exp_d;
Source src;
Sink snk;
Vlpf dut;
Model model;
SC_CTOR(Testbench) : src("src"), snk("snk"), dut("dut"), model("model") {
src.clk(clk);
src.xrst(xrst);
src.vld(i_vld);
src.d(i_d);
dut.clk(clk);
dut.xrst(xrst);
dut.i_vld(i_vld);
dut.i_d(i_d);
dut.o_vld(o_dut_vld);
dut.o_d(o_dut_d);
model.clk(clk);
model.xrst(xrst);
model.i_vld(i_vld);
model.i_d(i_d);
model.o_vld(o_exp_vld);
model.o_d(o_exp_d);
snk.clk(clk);
snk.xrst(xrst);
snk.i_dut_vld(o_dut_vld);
snk.i_dut_d(o_dut_d);
snk.i_exp_vld(o_exp_vld);
snk.i_exp_d(o_exp_d);
SC_THREAD(test);
}
void test() {
xrst = 0;
wait(25, SC_NS);
xrst = 1;
wait(src.done);
for (int i = 0; i < 10; i++) wait(clk.posedge_event());
std::cout << "#error(s) = " << snk.n_errors << std::endl;
sc_stop();
}
};
int sc_main(int argc, char *argv[]) {
Verilated::commandArgs(argc, argv);
sc_clock clk("clk", 10, SC_NS);
Testbench *tb = new Testbench("tb");
tb->clk(clk);
#ifdef DUMP_VCD
Verilated::traceEverOn(true);
VerilatedVcdSc *vcd = new VerilatedVcdSc();
tb->dut.trace(vcd, 99);
vcd->open("test_lpf.vcd");
sc_start();
vcd->close();
#else
sc_start();
#endif // #ifdef DUMP_VCD
return 0;
}
ざっくり説明すると、Sourceが作ったランダムデータを、検証対象とモデルModelに入力。そして、それらからの出力をSinkが受けて比較。そんな環境をTestbench階層で組み上げている。ついでに、SystemCのメイン関数であるsc_main()にて、Verilatorの波形ファイル作成機能を使用してみている。Verilatorを使ってテストを走らせるには、以下のコマンドを実行。
% verilator --sc lpf.v --trace --exe test_lpf.cpp
% cd obj_dir
% make -f Vlpf.mk
% ./Vlpf
う〜ん、何ともかったるい。Verilogなんて腐った言語は早くまともな言語に置き換えられてくれと思っているけど、SystemCはその器ではないかな。…って言うか、C++も言語仕様はかなり酷い部類だと思うし。
0 件のコメント:
コメントを投稿