Icarus verilog.vpp.学习.1
vvp的代码很长,我们可以先看重点: lexor.lex, parser.y, compile.cc, delay.cc, event.cc, schedule.cc和main.cc。处理位向量,把它们拼来接去,每位有01xz四种状态等等比较无聊。有点像时序电路和组合电路: 组合电路当然也复杂,但有种一根筋的感觉。:-)
vvp不是一边读入代码一边执行。
.thread T_0, $push;
%pushi/vec4 1, 0, 1;
%add;
%store/vec4 v000001e094cc40d0_0, 0, 1;
%jmp T_1.0;
T_1.1 ;
%end;
.thread T_1;
它是遇到.thread就创建线程(不是操作系统的线程,vvp是个单线程程序). .thread的代码则是夹在.thread之间的东西。
在lexor.lex里查%pushi和%add,都找不到。但是有:
"%"[.$_/a-zA-Z0-9]+ { yylval.text = strdup(yytext); return T_INSTR; }
即它俩的token的类型为T_INSTR,值则分别为%pushi和%add。腹诽"指令"前加%,.lex程序里本身就有很多%。
接着在parser.y里查T_INSTR:
| label_opt T_INSTR operands_opt ';'
{ compile_code($1, $2, $3); }
查compile_code, MadEdit, find+grep, ctags, cscope...等都可以多文件查找,但显然它应该在compile.cc里:
/*
* The parser uses this function to compile and link an executable
* opcode. I do this by looking up the opcode in the opcode_table. The
* table gives the operand structure that is acceptable, so I can
* process the operands here as well.
*/
void compile_code(char*label, char*mnem, comp_operands_t opa) {
vvp_code_t code = codespace_allocate();
code->opcode = op->opcode;
在*.h里查vvp_code_t: typedef struct vvp_code_s*vvp_code_t;
/*
* This is the format of a machine code instruction.
*/
struct vvp_code_s {
vvp_code_fun opcode;
union {
unsigned long number;
vvp_net_t *net;
vvp_code_t cptr;
vvp_array_t array;
class __vpiHandle*handle;
__vpiScope*scope;
const char*text;
};
union {
uint32_t bit_idx[2];
vvp_net_t *net2;
vvp_code_t cptr2;
class ufunc_core*ufunc_core_ptr;
};
};
再看下.thread: ".thread" { return K_THREAD; }
| K_THREAD T_SYMBOL ';'
{ compile_thread($2, 0); }
| K_THREAD T_SYMBOL ',' T_SYMBOL ';'
{ compile_thread($2, $4); }
/*
* When the parser finds a thread statement, I create a new thread
* with the start address referenced by the program symbol passed to
* me.
*/
void compile_thread(char*start_sym, char*flag)
最后, main()里: schedule_simulate();
void schedule_simulate(void) {
vpiNextSimTime();
while (ctim->start) {
struct event_s*cur = ctim->start->next;
if (cur->next == cur) {
ctim->start = 0;
} else {
ctim->start->next = cur->next;
}
cur->run_run();
delete (cur);
}
}
/* If there are no more active events, advance the event
queues. If there are not events at all, then release
the event_time object. */
if (ctim->active == 0) {
ctim->active = ctim->inactive;
ctim->inactive = 0;
if (ctim->active == 0) {
ctim->active = ctim->nbassign;
ctim->nbassign = 0; // nb是number的缩写. 它这个assign好像处理的是Verilog里的 <= 和assign.
vvp_fun_delay::vvp_fun_delay(vvp_net_t*n, unsigned width, const vvp_delay_t&d)
: net_(n), delay_(d) {
cur_real_ = 0.0;
if (width > 0) {
cur_vec4_ = vvp_vector4_t(width, BIT4_X);
cur_vec8_ = vvp_vector8_t(cur_vec4_, 6, 6);
schedule_init_propagate(net_, cur_vec4_);
} else {
schedule_init_propagate(net_, cur_real_);
}
delay()里要schedule. schedule要propagate event. fun是function或functor(像函数一样的对象)的缩写。
也许未必需要把vvp的输入a.out变成.c,吃透vvp的代码,修改后当个库来调用也许也可以。它的执行效率应该不低。
似乎仿真器可以用event loop来实现。每个always块就像ON_MESSAGE. 修改signal(不是Qt里的信号槽)后PostMessage. 某个signal的改变可能触发多个event handler,不能一个就把它消费掉,得都去调用。<= 可以用自动/手动插入临时变量来实现。assign可看作always @*,而且最后执行。event/message queue用critical section/mutex保护,多个thread/process同时运行。