2009年一月30日 星期五

脚本针对MIPS指令集,支持如下10条指令(可扩展):

add、sub、and、or、slt、lw、sw、addi、beq、j

测试程序如下:

main: addi $2, $0, 5
addi $3, $0, 12
addi $7, $3, -9
or $4, $7, $2
and $5, $3, $4
add $5, $5, $4
beq $5, $7, end
slt $4, $3, $4
beq $4, $0, around
addi $5, $0, 0
around: slt $4, $7, $2
add $7, $4, $5
sub $7, $7, $2
sw $7, 68($3)
lw $2, 80($0)
j end
addi $2, $0, 1
end: sw $2, 84($0)

转换后的机器码如下(十六进制):

20020005
2003000c
2067fff7
00e22025
00642824
00a42820
10a7000a
0064202a
10800001
20050000
00e2202a
00853820
00e23822
ac670044
8c020050
08000011
20020001
ac020054

附Perl脚本:assembler.rar

#!/usr/bin/perl

open FileIN,"$ARGV[0]";

chomp($_=$ARGV[0]);

/(.*)\.asm/;

open FileOUT,">$1.o";

$instr_addr=0;

while($line=<FileIN>){

  $_=$line;

  next if(!/[a-zA-Z0-9,\$]+/);

  chomp($_);

  if(/(.*)[ \t]*:.*/){

    $label_addr{$1}=$instr_addr;

  }

  $instr_addr+=4;

}

open FileIN,"$ARGV[0]";

$instr_addr=0;

while($line=<FileIN>){

  $_=$line;

  next if(!/[a-zA-Z0-9,\$]+/);

  chomp($_);

  if(/.*:(.*)/){

    $_=$1;

  }

  #for instructions like "XX XX, XX, XX"

  if(/[ \t]*([a-zA-Z]*)[ \t]*(.*)[ \t]*,[ \t]*(.*)[ \t]*,[ \t]*(.*)[ \t]*/) {

    $p1=$1; $p2=$2 ;$p3=$3; $p4=$4;

    $instr_code="";

    if($p1 eq "add") {

      $op=0;

      $p2=~/\$([0-9]*)/; $rd=$1;

      $p3=~/\$([0-9]*)/; $rs=$1;

      $p4=~/\$([0-9]*)/; $rt=$1;

      $instr_code=$op*2**26+$rs*2**21+$rt*2**16+$rd*2**11+32;

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

    if($p1 eq "sub") {

      $op=0;

      $p2=~/\$([0-9]*)/; $rd=$1;

      $p3=~/\$([0-9]*)/; $rs=$1;

      $p4=~/\$([0-9]*)/; $rt=$1;

      $instr_code=$op*2**26+$rs*2**21+$rt*2**16+$rd*2**11+34;

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

    if($p1 eq "and") {

      $op=0;

      $p2=~/\$([0-9]*)/; $rd=$1;

      $p3=~/\$([0-9]*)/; $rs=$1;

      $p4=~/\$([0-9]*)/; $rt=$1;

      $instr_code=$op*2**26+$rs*2**21+$rt*2**16+$rd*2**11+36;

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

    if($p1 eq "or") {

      $op=0;

      $p2=~/\$([0-9]*)/; $rd=$1;

      $p3=~/\$([0-9]*)/; $rs=$1;

      $p4=~/\$([0-9]*)/; $rt=$1;

      $instr_code=$op*2**26+$rs*2**21+$rt*2**16+$rd*2**11+37;

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

    if($p1 eq "slt") {

      $op=0;

      $p2=~/\$([0-9]*)/; $rd=$1;

      $p3=~/\$([0-9]*)/; $rs=$1;

      $p4=~/\$([0-9]*)/; $rt=$1;

      $instr_code=$op*2**26+$rs*2**21+$rt*2**16+$rd*2**11+42;

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

    if($p1 eq "addi") {

      $op=8;

      $p2=~/\$([0-9]*)/; $rt=$1;

      $p3=~/\$([0-9]*)/; $rs=$1;

      $addr=$p4;

      if($addr ge 0) {

        $instr_code=$op*2**26+$rs*2**21+$rt*2**16+$addr;

      } else {

        $instr_code=$op*2**26+$rs*2**21+($rt+1)*2**16+$addr;

      }

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

    if($p1 eq "beq") {

      $op=4;

      $p2=~/\$([0-9]*)/; $rs=$1;

      $p3=~/\$([0-9]*)/; $rt=$1;

      $addr=($label_addr{$p4}-$instr_addr-4)/4;

      if($addr ge 0) {

        $instr_code=$op*2**26+$rs*2**21+$rt*2**16+$addr;

      } else {

        $instr_code=$op*2**26+$rs*2**21+($rt+1)*2**16+$addr;

      }

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

  }

  #for instructions like "XX XX, XX(XX)"

  if(/[ \t]*([a-zA-Z]*)[ \t]*(.*)[ \t]*,[ \t]*([0-9]*)\((.*)\)[ \t]*/) {

    $p1=$1; $p2=$2; $p3=$3; $p4=$4;

    if($p1 eq "lw") {

      $op=35;

      $p2=~/\$([0-9]*)/; $rt=$1;

      $p4=~/\$([0-9]*)/; $rs=$1;

      $addr=$p3;

      if($addr ge 0) {

        $instr_code=$op*2**26+$rs*2**21+$rt*2**16+$addr;

      } else {

        $instr_code=$op*2**26+$rs*2**21+($rt+1)*2**16+$addr;

      }

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

    if($p1 eq "sw") {

      $op=43;

      $p2=~/\$([0-9]*)/; $rt=$1;

      $p4=~/\$([0-9]*)/; $rs=$1;

      $addr=$p3;

      if($addr ge 0) {

        $instr_code=$op*2**26+$rs*2**21+$rt*2**16+$addr;

      } else {

        $instr_code=$op*2**26+$rs*2**21+($rt+1)*2**16+$addr;

      }

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

  }

  #for instructions like "XX XX"

  if(/[ \t]*([a-zA-Z]*)[ \t]*(.*)[ \t]*/) {

    $p1=$1; $p2=$2;

    if($p1 eq "j") {

      $op=2;

      $addr=$label_addr{$p2}/4;

      $instr_code=$op*2**26+$addr;

      $out_code=&dec2hex($instr_code);

      print FileOUT "$out_code\n";

    }

  }

 

  $instr_addr+=4;

}

close FileIN;

close FileOUT;

 

sub dec2hex {

  $tmp=sprintf "%x",$_[0];

  $length=length($tmp);

  if($length != 8) {

    $i=8-$length;

    while($i) {

      $tmp="0$tmp";

      $i-=1;

    }

    sprintf "$tmp";

  } else {

    sprintf "%x",$_[0];

  }

}

2009年一月27日 星期二

用状态机实现时钟产生电路(分频)的Verilog代码示例


module clk_gen (clk,reset,clk1,clk2,clk4,fetch,alu_clk);

    input clk,reset;

    output clk1,clk2,clk4,fetch,alu_clk;

    wire clk,reset;

    reg clk2,clk4,fetch,alu_clk;

    reg[7:0] state;

    parameter S1 = 8'b00000001,

       S2 = 8'b00000010,

       S3 = 8'b00000100,

       S4 = 8'b00001000,

       S5 = 8'b00010000,

       S6 = 8'b00100000,

       S7 = 8'b01000000,

       S8 = 8'b10000000,

       idle = 8'b00000000;

 

    assign clk1 = ~clk;

 

    always @(negedge clk)

       if(reset)

           begin

           clk2 <= 0;

           clk4 <= 1;

           fetch <= 0;

           alu_clk <= 0;

           state <= idle;

           end

       else

           begin

           case(state)

           S1:

              begin

              clk2 <= ~clk2;

              alu_clk <= ~alu_clk;

              state <= S2;

              end

           S2:

              begin

              clk2 <= ~clk2;

              clk4 <= ~clk4;

              alu_clk <= ~alu_clk;

              state <= S3;

              end

           S3:

              begin

              clk2 <= ~clk2;

              state <= S4;

              end

           S4:

              begin

              clk2 <= ~clk2;

              clk4 <= ~clk4;

              fetch <= ~fetch;

              state <= S5;

              end

           S5:

              begin

              clk2 <= ~clk2;

              state <= S6;

              end

           S6:

              begin

              clk2 <= ~clk2;

              clk4 <= ~clk4;

              state <= S7;

              end

           S7:

              begin

              clk2 <= ~clk2;

              state <= S8;

              end

           S8:

              begin

              clk2 <= ~clk2;

              clk4 <= ~clk4;

              fetch <= ~fetch;

              state <= S1;

              end

           idle: state <= S1;

           default: state <= idle;        

           endcase

           end

endmodule

2009年一月24日 星期六

f
ive-stage pipelined processor with full hazard handling

 支持分支转移和无条件跳转指令,支持冒险检测和数据旁路。

(点击放大)

pipeline diagram

2008年九月4日 星期四

前面介绍的,并发过程之间的通讯可以使用事件和模块成员数据。但是这两种方法的使用需要十分仔细。SystemC提供一种“握手(handshake)”变量来实现过程间的通讯,称作通道(channel)。通道有primitive和hierarchical两种。Primitive Channel是一种没有包含层次和过程的通道。所有的primitive channel都是从sc_prim_channel基类继承而来。下面介绍最简单的三种primitive channel:sc_mutex、sc_semaphore、sc_fifo。

一、sc_mutex:

Mutex是互斥(mutual exclusion)的简称。Mutex在Elaboration阶段被创建,之后任何要使用该资源的过程都必须lock这个mutex以防止其他过程使用该共享资源。当该资源不再被使用时,过程需要unlock这个mutex。

SystemC通过sc_mutex通道来实现mutex。sc_mutex包含许多访问函数,包括阻塞和非阻塞两种风格。阻塞风格的函数只能在SC_THREAD过程中使用。sc_mutex的语法如下:

sc_mutex NAME;

NAME.lock(); // To lock the mutex NAME (wait until unlocked if in use)
NAME.trylock(); // Non-blocking, returns true if success, else false
NAME.unlock(); // To free a previously locked mutex

在实际的电子设计中,可以用sc_mutex来模拟共享总线的仲裁。当仲裁器设计好后再替换该sc_mutex。

二、sc_semaphore:

和mutex不同,有些资源能够有多个复制或者使用者。SystemC用sc_semaphore来模拟这种资源。其实sc_mutex可以看作是是有一个复制的sc_semaphore。sc_semaphore的语法如下:

sc_semaphore NAME(COUNT);

NAME.wait(); // To lock the mutex NAME (wait until unlocked if in use)
NAME.trylock(); // Non-blocking, returns true if success, else false
NAME.get_value(); // Returns number of available semaphores
NAME.post(); // To free a previously locked mutex

用sc_semaphore模拟多端口存储器的例子如下:

class mutiport_RAM {
  sc_semaphore read_ports(3);
  sc_semaphore write_ports(2);
  ...
  void read(int addr, int& data) {
    read_ports.wait();
    // perform read
    read_ports.post();
  }
  void write(int addr, int data) {
    write_ports.wait();
    // perform write
    write_ports.unlock();
  }
}

三、sc_fifo:

FIFO是用来管理数据流的最常用的数据结构。SystemC提供sc_fifo来实现FIFO。sc_fifo<>在默认情况下的深度是16,另外还需要指定元素的数据类型。sc_fifo的语法如下:

sc_fifo<ELEMENT_TYPENAME> NAME(SIZE);

NAME.write(VALUE);
NAME.read(REFERENCE);
...=NAME.read();
if(NAME.nb_read(REFERENCE)) { // Non-blocking, true if success
  ...
}
if(NAME.num_available()==0)
  wait(NAME.data_written_event());
if(NAME.num_free()==0)
  next_trigger(NAME.data_read_event());

对于复杂数据类型的元素,将指针传给sc_fifo将会更加高效。

2008年八月24日 星期天

实系统中很多活动都是同时发生的。SystemC用过程(Process)来模拟并发性。了解SystemC仿真器如何处理并发性,能使设计者写出高效的仿真模型。SystemC提供两种主要的过程类型:SC_THREAD和SC_METHOD。另外还有第三种,SC_CTHREAD,是SC_THREAD的变型。SystemC官方的Language Reference Manual不推荐在一般情况下使用SC_CTHREAD。

一、事件sc_event:

事件(Event)是SystemC这种事件驱动的仿真器的关键之处。事件就是在某个特定时间点发生的事情。一个时间没有取值,也没有持续时间。事件本身没有并不做任何可以被观察到的事情,它只是激发对它敏感的过程,从而表现出作用。SystemC中的过程通过动态或者静态敏感表来等待一个事件的发生。声明一个事件的语法如下:

sc_event name;

二、过程SC_THREAD:

过程SC_THREAD被且只被仿真器启动一次。SC_THREAD一旦被启动就完全控制仿真过程,直到其自己将控制返回给仿真器。SC_THREAD有两种返回控制给仿真器的方法,一种是简单退出(如return),这意味着永远结束该过程。因此,常常在SC_THREAD中使用含有wait语句的无限循环。

另一种返回控制的方法是使用wait来挂起过程。有时候wait并不是被直接使用的,比如sc_fifo的阻塞读和写在FIFO分别为空或满的时候将隐式的激活wait。

三、SC_THREAD::wait()的动态敏感表(dynamic sensitivity):

SC_THREAD依靠wait来挂起自身。当wait被执行时,当前SC_THREAD过程的状态将被保存,同时仿真内核得到控制权,接着启动另一个准备好的过程。当被挂起的过程重新被启动时,它将从wait后的语句开始执行。wait的语法如下:

wait(time);
wait(event);
wait(event1 | event2);  //  any of these events
wait(event1 & event2);  //  all of these events
wait(timeout, event1 | event2);  //  any of these events with timeout
wait(timeout, event1 & event2);  //  all events with timeout
wait();  //  static sensitivity

带有timeout的wait将timeout值设为最大的等待时间,超过这个时间即使事件没有发生,过程同样也会被重新启动。我们可以在timeout的wait后面用布尔函数time_out()来检测到底是不是timeout使得过程返回,例如:

...
sc_event ack_event, bus_error_event;
...
wait(t_MAX_DELAY, ack_event | bus_error_event);
if(time_out()) break;  //  test for timeout
...

四、启动事件-.notify():

事件通过notify()来显式的发生。notify()有以下两种语法风格,由于C++是面向对象的语言,因此建议使用前一种语法风格:

// Object-oriented style  (preferred)
event_name.notify();  //immediate notification
event_name.notify(SC_ZERO_TIME);  //delayed notification
event_name.notify(time);  //timed notification

// Functional-call style
notify(event_name);  //immediate notification
notify(event_name, SC_ZERO_TIME);  //delayed notification
notify(event_name, time);  //timed notification

上面最难理解的是.notify()和.notify(SC_ZERO_TIME)两种的区别。.notify()会立即使等待该事件的过程转为等待被执行。而.notify(SC_ZERO_TIME)会在当前等待被执行的所有过程完成后,才将等待该事件的过程转为等待被执行(所谓的下一个delta-cycle)。

对于.notify(time)来说,如果有多个notification,那么只有时间最近的那个是有效的。我们可以用.cancel()来取消所有notification。

五、过程SC_METHOD:

SC_METHOD与SC_THREAD的主要不同,是SC_METHOD不能在其内部被挂起,即不能直接或间接的使用wait(),否则会报runtime error的错误。SC_METHOD一旦运行便会运行到底,然后返回。仿真引擎根据动态敏感表不断的调用SC_METHOD。某种程度上,SC_METHOD很像Verilog中的always块。注册SC_METHOD过程的语法如下:

SC_METHOD(process_name);  //Located inside constructor

SC_METHOD中的变量在其每次被调用时都要被声明和初始化。这和SC_THREAD中变量是永远存在的不一样。如果希望保存SC_METHOD中的值,那么就需要使用SC_MODULE中定义的局部变量。

六、SC_METHOD::next_trigger()的动态敏感表:

SC_METHOD过程通过next_trigger()来处理动态敏感表。next_trigger()和wait()有着同样的语法。next_trigger不会阻碍当前SC_METHOD的执行,它会改变下一次调用该SC_METHOD的敏感表。如果没有静态敏感表,那么SC_METHOD的所有执行分支都应当有next_trigger,否则可能导致该SC_METHOD不再被调用。

七、过程的静态敏感表(Static Sensitivity):

前面说的动态敏感表示在仿真阶段的建立的,并且可以在仿真中被改变。静态敏感表是在仿真开始前建立的,在仿真过程中不能被改变。静态敏感表的申明有两种语法:

sensitive<<event[<<event]...;  // streaming style
sensitive(event [, event]...);  //  functional style

同样建议选择类似于C++流的第一种语法风格。静态敏感表的申明必须紧跟在其过程的注册语句后面。

八、dont_initialize:

SystemC的仿真引擎将在开始时初始化执行所有的过程,然后有时候某些过程不需要在一开始就执行。可以在静态敏感表后面加上dont_initialize()来阻止初始化执行。

九、sc_event_queue:

正如前面讲的,sc_event只能被安排一次,如果多个notification只有时间最近的那个会被执行。sc_event_queue使得一个事件能够被安排多次,甚至是在同一个时间点。sc_event_queue的语法和sc_event类似。

2008年八月22日 星期五

解SystemC中的时间概念,对于弄清仿真行为有着很大的帮助。

一、sc_time:

SystemC提供称为sc_time的数据来行来衡量时间。时间由长度(magnitude)和单位(unit)表达,语法如下:

sc_time name;  //no initialization
sc_time name(magnitude, unit);

时间单位有以下几种:SC_SEC、SC_MS、SC_US、SC_NS、SC_PS、SC_FS。SystemC允许对sc_time对象进行加、减以及比例缩放等操作。一个特殊的常量SC_ZERO_TIME可以用来表示sc_time(0, SC_SEC)。

二、sc_start():

sc_start()在SystemC中用于启动仿真阶段。可以给sc_start()提供一个时间参数用来指定最大仿真时间,如:

sc_start(60.0, SC_SEC);

三、sc_time_stamp()和显示时间:

在SystemC中可以通过sc_time_stamp()来获得仿真当前时间,例如:

cout<<sc_time_stamp()<<endl;

四、wait(sc_time):

wati()被用在SC_THREAD过程中完成延迟功能。当遇到wait(),SC_THREAD过程被阻止执行,并且在指定的时间返回继续执行,如下面的例子:

void simple_process_ex::my_thread_process(void)  {
  wait(10, SC_NS);
  cout<<"Now at "<<sc_time_stamp()<<endl;
  sc_time t_DELAY(2, SC_MS);
  t_DELAY*=2;
  cout<<"Delaying "<<t_DELAY<<endl;
  wait(t_DELAY);
  cout<<"Now at "<<sc_time_stamp()<<endl;
}

上面例子的运行结果为:

Now at 10 ns
Delaying 4 ms
Now at 4000010 ns