SystemC入门学习-第5章 同步逻辑建模

本章重点学习同步逻辑中的触发器,锁存器的一些建模规范:

  • 触发器建模
  • 带异步置位/复位
  • 带同步置位/复位
  • 锁存器建模

5.1 触发器建模

        触发器建模的关键是敏感列表的规范。SC_MODULE的规范写法中出现过sensitive 参数列表是事件敏感, 对触发器建模来讲,SC_MODULE的结构不变,只需要将事件敏感修改为沿敏感,写法如下, 这样的沿可用于指定bool或者sc_logic类型的信号或者端口。就sc_logic类型而言,非0到0的跳变被认为是负跳变沿,非1到1的跳变被认为是正跳变沿。

//规定用正跳变沿
sensitive_pos

//规定用负跳变沿
sensitive_neg

        下面是一个D触发器模型, 敏感列表包含了指定于端口clk的沿敏感sensitive_pos, 表明只有在端口clk的正跳变沿时刻,数据输入d才得以传送到输出q。

//file basic_ff.h
#include "systemc.h"

SC_MODULE(basic_ff){
    sc_in <bool> d, clk;
    sc_out <bool> q;

    void prc_basic_ff();
    
    SC_CTOR(basic_ff){
        SC_METHOD(prc_basic_ff);
        sensitive_pos << clk;
    }

};

//file basic_ff.cpp
#include "basic_ff.h"

void basic_ff::prc_basic_ff(){
    q = d;
}

        下面是一个包含多位端口的触发器示例,为prc_gang_ffs指定了负跳变沿敏感,所以这是一个由负跳变沿触发的触发器。

#include "systemc.h"

const int WIDTH =4;

SC_MODULE(gang_ffs){
    sc_in <sc_uint<WIDTH> > current_state;
    sc_in <bool> clock;
    sc_out <sc_uint<WIDTH> > next_state;

    void prc_gang_ffs();
    
    SC_CTOR(gang_ffs){
        SC_METHOD(prc_gang_ffs);
        sensitive_neg << clock;
    }

};

void gang_ffs::prc_gang_ffs(){
    next_state = current_state;
}

5.2 多进程

        一个模块可以具有多个SC_METHOD进程。每个进程可以模拟组合逻辑(使用事件敏感),也可以模拟同步逻辑(使用沿敏感)。进程之间的通信联系必须使用信号。

        一个进程不能既有沿敏感,又有事件敏感,并且在沿敏感的规范中只可以使用一个bool型的信号或者端口。

        下面是一个流水线的序列检测器。这个例子中,触发器用于生成信号。当输入数据流检测到‘101’序列时,检测器的输出为1。综合后的逻辑如下图。

#include "systemc.h"

SC_MODULE(seq_det){
    sc_in <bool> clk, data;
    sc_out <bool> seq_found;

    void prc_seq_det();//同步逻辑进程
    void prc_output();//组合逻辑进程

    sc_signal <bool> first, second, third;

    SC_CTOR(seq_det){
        SC_METHOD(prc_seq_det);
        sensitive_pos <<clk;

        SC_METHOD(prc_output);
        sensitive << first << second << third;

    }

};

void seq_det::prc_seq_det(){
    first = data;
    second = first; 
    third = second;
}

void seq_det::prc_output(){
    seq_found = first & !second & third;
}

        需要注意到是,信号和端口赋值的∆延迟。对信号和端口的赋值不是立即发生的,而是总发生在一个∆延迟之后。进程之间是可以同步执行的,即当时钟正跳变沿到来时候,进程prc_seq_det被调用,first被赋值,但是second = first, third = second没有执行时候,first的变化导致了进程prc_output的执行,所以此刻second是上个时刻的值,third是上上个时刻的值,所以如果序列中出现了101,就能被检测出来了。

5.3 带异步置位和清零端的触发器

        带异步置位和清零端的触发器编写时,清零输入的正或负跳变沿被指定为沿敏感列表的一部分。即若异步清零为低电平有效,则指定使用负跳变沿;若高电平有效,则使用正跳变沿。下面是一个带异步清零端的增/减计数器。

#include "systemc.h"

const int COUNT_SIZE = 4;
SC_MODULE(count4){
    sc_in <bool> mclk, clear, updown;
    sc_out <sc_uint <COUNT_SIZE> > data_out;

    void sync_block();

    SC_CTOR(count4){
        SC_METHOD(sync_block);
        sensitive_pos << mclk;
        sensitive_neg << clear;
    }

};

void count4::sync_block(){
    if(!clear)  //符合异步条件
        data_out = 0;
    else          //不符合,使用时钟的正跳变沿,即mclk
        if(updown)
            data_out = data_out.read() + 1;
        else
            data_out = data_out.read() - 1;
}

        下面是一个包含异步置位和清零位的触发器建模。置位和清零的输入沿必须指定为敏感列表的一部分。

#include "systemc.h"

const int STATE_BITS = 4;

SC_MODULE(async_states){
    sc_in <bool> clk, reset, set;
    sc_in <sc_uint <STATE_BITS> > current_state;
    sc_out<sc_uint <STATE_BITS> > next_state;

    void prc_async_state();

    SC_CTOR(async_states){
        SC_METHOD(prc_async_state);
        sensitive_neg << clk <<reset;
        sensitive_pos <<set;
    }

};

void async_states::prc_async_state(){
    if (!reset)   //第一个异步条件
        next_state = 0;
    else if(set)  //第二个异步条件
        next_state = 5;
    else          //(隐含的)负跳变沿
        next_state = current_state;
}

        通常情况下,一个进程可以有多个被指定为其敏感列表的跳变沿。并且使用一条这样形式的if语句来描述该进程的行为,在该形式的语句中首先检查的不是时钟条件,而在最后那个else分支中才隐含了时钟条件。若指定使用负跳变沿敏感列表,则还需要使用非时钟条件的逻辑“非”;否则就要使用非时钟条件的正值。如下是这种条件语句的样板程序。

SC_METHOD(my_process);
sensitive_pos << a << b << clk;
sensitive_net << d << e << f;

void my_module::my_process(){
    if(a)
        <异步行为>
    else if (b)
        <异步行为>
    else if (!d) //因为指定了负跳变沿,所以使用逻辑“非”
        <异步行为>
    else if (!f)
        <异步行为>
    else        //时钟的正跳变沿
        <按时钟节拍的行为>
}

5.4 带同步置位和清零端的触发器

        在未带同步置位和清零端得触发器建模时,在敏感列表中只需指定一个时钟跳变沿即可。其置位和清零条件被清晰明确地编写在SC_METHOD进程的代码里。

        如下是一个带低电平有效同步置位端的计数器。(prc_counter代码,书上写了未出现过的变量名:preset,不知道是写错了还是自己哪里没理解清)

#include "systemc.h"

const int COUNT_BITS = 4;

SC_MODULE(sync_count4){
    sc_in <bool> mclk, clear, updown;
    sc_in <sc_uint<COUNT_BITS> > data_in;
    sc_out<sc_uint<COUNT_BITS> > data_out;

    void  prc_counter();
    
    SC_CTOR(sync_count4){
        SC_METHOD(prc_counter);
        sensitive_pos << mclk;
    }
};

void sync_count4::prc_counter(){
    if(!preset)
        data_out = data_in;
    else
        if(updown)
            data_out = data_out.read()+1;
        else
            data_out = data_out.read()-1;
}

同步逻辑如下图:

5.5 多时钟和多相位时钟

        在一个模块中,可以编写任意多个SC_METHOD进程,每个这样的进程都可以是同步或组合的进程。当出现多个同步进程时,可以在不同进程中使用不同的时钟来为设计的逻辑建模。

        下面示例,进程prc_vt15ck在时钟vt15ck的负跳变沿触发,而进程prc_ds1ck则在时钟dslck的正跳变沿触发。

#include "systemc.h"

SC_MODULE(mult_clks){
    sc_in <bool> vt15ck, addclk, adn, resetn, subclr, subn, ds1ck;
    sc_out <bool> ds1_add, ds1_sub;

    void prc_vt15ck();
    void prc_ds1ck();
    sc_signal <bool> add_state, sub_state;

    SC_CTOR(mult_clks){
        SC_METHOD(prc_vt15ck);
        sensitive_neg << vt15ck;
        SC_METHOD(prc_ds1ck);
        sensitive_pos << ds1ck;
    }

};

void mult_clks::prc_vt15ck(){
    add_state = !(addclk | (adn | resetn));
    sub_state = subclr ^ (subn & resetn);
}

void mult_clks::prc_ds1ck(){
    ds1_add = add_state;
    ds1_sub = sub_state;
}

        如下是综合后生成的逻辑。信号add_state和信号sub_state在时钟vt15ck的负跳变沿时刻被赋值,并且它们的值在另外一个时钟ds1ck的正跳变沿时刻被赋予ds1_add和ds1_sub。在RTL建模时,通常规定不允许使用不同时钟沿对同一个信号或端口赋值

        

        在设计模块中可以使用同一个时钟的不同相位。下面举例说明。进程prc_rising由时钟zclk的正跳变沿触发,进程prc_falling则由时钟zclk的负跳变沿触发。一个进程的输出是另一个进程的输入,用信号d在进程之间通信实现。

#include "systemc.h"

SC_MODULE(multiphase)
{
    sc_in <bool> zclk, a, b, c;
    sc_out <bool> e;

    void prc_rising();
    void prc_falling();

    sc_signal <bool> d;

    SC_CTOR(multiphase){
        SC_METHOD(prc_rising);
        sensitive_pos << zclk;

        SC_METHOD(prc_falling);
        sensitive_neg << zclk;
    }

};

void multiphase::prc_rising(){
    e = d & c;
}

void multiphase::prc_falling(){
    d = a & b;
}

5.6 锁存器建模

        如果没有对进程的所有路径赋值,则在该进程综合后的逻辑中有可能为信号或端口生成锁存器。条件语句如if , switch,是可能在进程内生成多条执行路径的两条语句。

        if语句生成锁存器的例子1:

        当clk为0时候,输出端口z没有被赋值。因此根据该模型的定义,综合后将为端口z生成一个锁存器。

#include "systemc.h"

SC_MODULE(latched_alu){
    sc_in <bool> clk, a,b;
    sc_out <bool> z;

    void prc_alu();

    SC_CTOR(latched_alu){
        SC_METHOD(prc_alu);
        sensitive_pos << clk << a << b;
    }

};

void latched_alu::prc_alu(){
    if(clk)
        z = !( a | b);

}

        switch语句生成锁存器的一个例子2:

#include "systemc.h"

enum states{s0, s1, s2, s3};
const int Z_SIZE = 2;

SC_MODULE(state_update){
    sc_in <states> current_state;
    sc_out <sc_uint<Z_SIZE> > z;

    void prc_state_update();

    SC_CTOR(state_update){
        SC_METHOD(prc_state_update);
        sensitive << current_state;
    }

};

void state_update::prc_state_update(){
    switch(current_state)
    {
        case s0:
        case s3: z=0; break;
        case s1: z = 3; break;
    }

}

避免生成锁存器

        在大多数情况下,生成锁存器是不需要的。所以避免生成锁存器的关键是:若信号或端口在条件语句中被赋值,则必须确认条件语句的所有可能分支中,该信号或端口都被赋予一个值

也就是,代码覆盖所有的条件分支,或者给端口或信号赋初值。

比如上面示例2,给prc_state_update函数修改如下:

//修改方案1
void state_update::prc_state_update(){
    switch(current_state)
    {
        case s0:
        case s3: z = 0; break;
        case s1: z = 3; break;
        default: z = 1; break;
    }

}

//修改方案2
void state_update::prc_state_update(){
    z = 1; 
    switch(current_state)
    {
        case s0:
        case s3: z = 0; break;
        case s1: z = 3; break;
    }

}

         另一个完整示例:

        下面示例的prc_compute1, prc_compute是生成锁存器和不生成锁存器的写法,以及其综合后的逻辑图对比:

#include "systemc.h"
const int BITS = 2;
enum grade_type { fail, pass, excellent};

SC_MODULE(compute){
    sc_in <sc_uint<BITS> >marks;
    sc_out <grade_type> grade;

    void prc_compute();

    SC_CTOR(compute){
        SC_METHOD(prc_compute);
        sensitive << marks;
    }

};

void compute::prc_compute(){
    if(marks.read() < 5)
        grade = fail;
    else if (marks.read()<7)
        grade = pass;
    else
        grade = excellent;

}

void compute::prc_compute1(){
    if(marks.read() < 5)
        grade = fail;
    else if (marks.read()<7)
        grade = pass;

}

 

5.7 小结

  • 同步逻辑建模使用跳变沿敏感的SC_METHOD进程
  • 一个模块可以包含任意多个进程,每个进程可以是表示组合逻辑的进程,或者是表示同步逻辑的进程
  • 若在一个对时钟跳变沿敏感的进程中对信号或者端口进行赋值,则综合后生成的逻辑为触发器
  • 同步逻辑中的异步置位和复位可以使用特定形式的if语句建模
  • 必须在if语句或开关语句的所有分支中对信号或端口赋值,否则综合后将生成锁存器
  • 若在if语句或开关语句前对信号或端口进行初始化,或者确保在条件语句的所有分支中都对其进行赋值,则综合后可以避免生成锁存器。