• SystemC入门学习-第3章 数据类型


            本章将详细的描述SystemC的数据类型,并介绍这些类型的数据可以进行哪些操作。比如值保持器(value holder)就是一种特殊的类型。在所有的类型中,最重要的是bool和sc_uint两种类型

    3.1 值保持器

    值保持器有三种:

    • 变量:在函数中声明并使用。
    • 信号:被用于进程之间的通信以及模块实例之间的连接。用sc_signal声明
    • 端口:被用于指定与模块的接口。用sc_in, sc_out, sc_inout之一声明。
    1. type variable_name1,variable_name2,...;
    2. //for example
    3. int mpy;
    4. sc_signal signal_name1, signal_name2,...;
    5. //for example
    6. sc_signal <bool> mask;
    7. sc_in input_name1,input_name2,...;
    8. sc_out output_name1,output_name2,...;
    9. sc_inout inout_name1,inout_name2,...;
    10. //for example
    11. sc_out4> > addi[6];

            注意,和标准的C++一样,不能只用一条赋值语句完成对多维数组的赋值。必须逐个元素对数组赋值。

    1. sc_signal[bool] mask[256][16];
    2. for(i=0; i<256; i++)
    3. for(j=0; j<16; j++)
    4. mask[i][j]=false;

    3.2 类型的总结

            如下是SystemC RTL 支持的SystemC 数据类型和C++数据类型。C++的数据类型具体位宽和计算机操作系统平台有关。这些数据类型可以用来声明变量、信号和端口。

    3.3 位类型

            用sc_bit声明位类型。该类型的取值为‘0’或‘1’, ‘0’表示假,‘1’表示真。

            在任何布尔类型逻辑操作或者赋值中,位类型操作数与bool类型操作数可以自由混合。

    下面是位类型的操作符:

    3.4 任意位宽类型

            类型sc_bv定义了一个任意位宽的位向量,这是一个类型为sc_bit的向量,该向量的位宽在类型定义时被指定。该向量最右边的序号为0,即最小位。宽度W设置了向量的位宽,最高位为W-1位,一直到最低位0位。

            下面是几个例子,注意在表示位宽的WIDTH的记号与最后那个尖括号“>”之间必须添加一个空格字符。

    1. sc_bv<8> ctrl_bus;//声明ctrl_bus是一个8位的向量,位序号从7到0,ctrl_bus[0]是最低位
    2. sc_out4> > mult_out; //声明了变量mult_out为位宽4的向量输出端口,位序号从3到0
    3. sc_bv<4> mult;
    4. ctrl_bus = "10110000";
    5. ctrl_bus = "10011"; //不够8位,左边补0,即变成“00010011”
    6. mult_out = "1011";

            下面列出了支持位向量操作数的操作符和方法。

            比较特别的操作是位选择操作符[], 拼接操作符(,),范围选择方法range()和六个缩减操作方法。

            range()被用来获得向量的位范围。range()方法中的范围索引可以是递增的,也可以使递减的。

            缩减“与”方法and_reduce()对向量的所有位进行逻辑“与”操作,然后返回1位的结果。其它几个缩减方法类似。

    示例如下:

    1. ctrl_bus[5] = '0';
    2. ctrl_bus.range(0,3) = ctrl_bus.range(7,4);//ctrl_bus的第7位赋值给第0位,第6位赋值给第1位,第5位赋值给第2位,第4位赋值给第3位
    3. mult = (ctrl_bus[0], ctrl_bus[0], ctrl_bus[0], ctrl_bus[1]);
    4. ctrl_bus[0] = ctrl_bus.and_reduce();
    5. ctrl_bus[1] = mult.or_reduce();

            位操作符和range()方法只适用于变量,不能用于端口或者信号。若必须对端口或者信号进行位选择或者范围选择,则必须使用一个临时变量。

    使用示例如下:

    1. sc_signal4> > dval;
    2. sc_in 8> > addr;
    3. sc_bv<4> var_dval;
    4. sc_bv<8> var_addr;
    5. sc_bit ready;
    6. //读输入地址addr的第2位
    7. var_addr = addr.read();
    8. ready = var_addr[2];
    9. //把‘011’赋予信号dval的若干位
    10. var_dval = dval;
    11. var_dval.range(0,2) = "011";
    12. dval = var_dval;

            对位向量类型不允许进行算术运算。为了支持这种运算,位向量类型的操作数可以先被赋予一个有符号的或者无符号的整型变量,进行必要的算术运算,然后将运算结果转回到原来的位向量。为了允许位向量和整型变量之间的转换,赋值可被重载。

    1. sc_in 4> > pha1;
    2. sc_signal6> > pha2;
    3. sc_uint<4> uint_pha1;
    4. sc_uint<6> uint_pha2;
    5. uint_pha1 = pha1;
    6. uint_pha2 = pha2;
    7. uint_pha2 = uint_pha2 - uint_pha1;
    8. pha2 = uint_pha2;

            声明局部变量时候,可以将其所有位的值一起初始化为‘1’,或者其它任意值。

    1. //将所有位初始化为‘1’
    2. sc_bv<8> all_ones('1);
    3. sc_bv<4> dtack(true);
    4. //将变量初始化为任意值
    5. sc_bv<8> test_pattern ="01010101";
    6. sc_bv<4> wbus = "0110";

    3.5 逻辑类型

            逻辑类型用sc_logic声明。该类型共有四个值:

    • '0', SC_LOGIC_0 假;
    • '1', SC_LOGIC_1 真;
    • 'X', SC_LOGIC_X 不确定;
    • 'Z', SC_LOGIC_Z 高阻抗;

    下面是这种类型操作数的操作符:

            在使用赋值、相等和不相等操作符时,sc_logic类型的操作符可以与sc_bit类型的操作符自由混用

    1. sc_logic pulse, trig;
    2. sc_bit select;
    3. sc_signal stack_end;
    4. sc_inout load_stack;
    5. stack_end = SC_LOGIC_Z; //赋高阻抗值
    6. pulse != select; //sc_bit类型与sc_logic类型可以进行比较
    7. select = trig; //sc_logic类型可以赋值给sc_bit类型,若trig是“X”或者“Z”将发出警告
    8. load_stack = SC_LOGIC_X ; //给双向端口赋‘X’值

            逻辑类型的操作数按位操作的行为可用下面的表格定义:

            在某些场合,可以明确地给sc_logic类型赋值。也可以用to_bool()方法将sc_logic型的逻辑值转换为bool型逻辑值。

    1. sc_logic('Z');
    2. static_case ('Z');
    3. trig = SC_LOGIC_Z; //和下面一句等价
    4. trig = sc_logic('Z');
    5. bool wrn;
    6. sc_logic pena(SC_LOGIC_1);
    7. wrn = pena.to_bool();

    3.6 任意位宽的逻辑类型

            sc_lv类型可以定义任意位宽的逻辑向量(其中逻辑位为‘0’,‘1’,‘X’, 'Z')。sc_lv是一种类型为sc_logic的逻辑向量。该向量位宽由类型定义指定。最右边位序是0, 为最低位。W设置了向量的位宽,从第W-1位到底0位,第W-1位为最高位。

            sc_lv类型的值包含一系列逻辑值为“0”,“1”,“X”,“Z”的字符串指定。也可以加“0d”,"0x"指定为十进制或者十六进制字符串。如下示例。

    1. sc_lv<4> data_bus; //位宽为4的逻辑向量
    2. sc_signal8> > counter_state; //8位的逻辑向量
    3. sc_out16> > sensor;
    4. data_bus = "0011";
    5. data_bus = "0d14"; //指定为十进制数值
    6. sensor = "10110XX011000ZZZ";
    7. sc_lv<8> dtack_read = "0XFE"; //指定为十六进制数值
    8. sc_lv<4> coh_rd ="0XA";

            把数值赋予逻辑向量时候,若值的位宽与等号左边逻辑向量的位宽不符,根据数值的位宽大于或者小于逻辑向量的位宽,在数值的高位截断多余的高位或者添加“0”扩展数值。

            局部变量可以在其声明期间,将所有位初始化为“Z”值或者“X”值。

    1. sc_lv<8> allzs(SC_LOGIC_Z);
    2. sc_lv<4> allxs(SC_LOGIC_X);
    3. sc_lv<4> mbpc('0');
    4. sc_lv<8> prog_ctr(SC_LOGIC_1);
    5. sc_lv<4> as_byte(true);

            支持逻辑向量操作数的操作符与支持位向量操作数的操作符完全一样。与位向量一样,逻辑向量的端口和信号也不能直接进行位选和部分位选,所以必须借助一个临时变量来完成所想要实现的赋值。

    1. sc_out8> > bmask;
    2. sc_lv<8> temp;
    3. sc_logic rx_ok, tx_ok;
    4. temp = bmask;
    5. temp[4] = rx_ok;
    6. temp[7] = tx_ok;
    7. bmask = temp;
    8. sc_signal 4> > sabm;
    9. sc_logic sel_bit;
    10. sc_lv<4> q_temp;
    11. q_temp = sabm;
    12. sel_bit = q_temp[3];

            不允许对逻辑向量类型进行算术运算。若想进行算术运算,必须首先把逻辑向量类型的操作数赋予有符号或者无符号的整型数,然后进行算术运算,再把运算结果返回到逻辑向量。

    1. sc_lv<4> index1;
    2. sc_lv<8> index2;
    3. sc_int<4> int_index1;
    4. sc_int<8> int_index2;
    5. int_index1 = index1;
    6. int_index2 = index2;
    7. int_index2 += int_index1;
    8. index2 = int_index2;

            赋值被重载以允许逻辑向量与整型数之间互相转换。位向量和逻辑向量可以互相赋值。若在赋值期间,被赋予整型数或者位向量的值中包含“X”或“Z”,则结果是不确定的,会在运行时发出报警。

    1. sc_uint<4> driver;
    2. sc_int<8> q_array;
    3. driver = data_bus; //给无符号整型数赋逻辑向量
    4. q_array = data_bus; //若data_bus存在“X ”,"Z", 将在运行时发出报警,赋值结果不确定
    5. sensor = q_array; /因为右式是符号值(逻辑向量无符号),所以q_array左边多余的位都用“0”补上
    6. data_bus = driver; //给逻辑向量赋无符号整数值
    7. int srw; //用to_int()方法将逻辑向量转换为整型数
    8. sc_lv<8> crd_value;
    9. src = data_bus.to_int();
    10. driver.range(2,0) = crd_value.range(7,5).to_int();
    11. q_array.range(7,2) = crd_value.range(1,6).to_int();

    3.7 有符号的整数类型

            sc_int用来定义有符号的整数类型。这是一个有固定精度的整数类型,其最高精度为64位。整数类型的位宽可以明确指定。本类型被解释为有符号的整数类型,其值用2的补码形式表示。位宽为W的sc_int整数类型,其第W-1位为符号位,最低位为第0位。

            所有按位操作符对整型量的操作都使用与该整型量等价的位向量实现。整型量的某一位可使用位选择操作符[]访问。整数类型的范围可使用range()方法访问。缩减操作符对整数类型对应的向量进行缩减运算产生1位的结果。concat()函数也能被用于进行拼接操作。

    1. sc_int<4> sel_addr, inc_pc;
    2. sc_int<8> opcode;
    3. sc_int<12> sel_data;
    4. #define N 7
    5. sc_in1> > cpu_control[4];
    6. sc_int<16> hr_reg_file[32];
    7. opcode = sel_addr + inc_pc;
    8. sel_data = -12;
    9. opcode = sel_data << 2;
    10. sel_addr = 6;
    11. inc_pc = -5;
    12. sel_addr = sel_addr ^ inc_pc;
    13. sel_data = 100;
    14. inc_pc = sel_data.range(3,0);
    15. opcode.range(1,0) = (sel_data[6], sel_data[7]);
    16. hr_reg_file[2] = concat(sel_data, sel_addr);
    17. bool stop_clk;
    18. bool start_clk;
    19. stop_clk = inc_pc.and_reduce();
    20. start_clk = hr_reg_file[15].xor_reduce();

            sc_int类型与C++整型是兼容的,并且能互相交换使用。使用to_int()方法可以将位向量或者逻辑向量转换成有符号的整型值。

    1. sc_bv<8> tic;
    2. sc_int<4> itf;
    3. sc_int<8> int_tic;
    4. sc_int<4> int_itf;
    5. int_tic = tic.to_int();
    6. int_itf = itf.to_int();

            若想以位的形式打印该有符号整型值,必须将该变量赋予位向量。如下:

    cout << "Select address bus has "<< (sc_bv<4>)sel_addr <

            to_string()方法可被用来打印出不同格式的整型值。可供选择的打印格式种类如下:

    1. SC_BIN 打印出二进制的2的补码
    2. SC_BIN_US  打印出无符号数的二进制码
    3. SC_BIN_SM 打印出有符号值的二进制码
    4. SC_CSD  打印出规范的有符号数字码
    5. SC_OCT  打印出八进制码
    6. SC_HEX  打印出十六进制码
    7. SC_DEC  打印出十进制码

    示例:

    1. sc_int<8> rx_data =106;
    2. sc_int<4> tx_buf = -11;
    3. cout << "Default: rx_data = " << rx_data.to_string() <
    4. cout << "Binary: rx_data = " << rx_data.to_string()(SC_BIN) << endl;
    5. cout << "Binary unsigned: rx_data = " << rx_data.to_string()(SC_BIN_US) << endl;
    6. cout << "Binary signed magnititude: rx_data = " << rx_data.to_string()(SC_BIN_SM) << endl;
    7. cout << "Canonical signed: tx_buf = " << tx_buf.to_string()(SC_CSD) << endl;
    8. cout << "Octal: rx_buf= " << tx_buf.to_string()(SC_OCT) << endl;
    9. cout << "Hexadecimal: rx_buf= " << tx_buf.to_string()(SC_HEX) << endl;
    10. cout << "Decimal: rx_buf= " << tx_buf.to_string()(SC_DEC) << endl;

    3.8 无符号的整数类型

            无符号整数类型用sc_uint表示。这是一个固定精度的整数类型,其最大位宽为64位。整数类型的位宽可以明确指定。sc_uint类型被解释为无符号的整数类型。位宽为W, 类型sc_uint的整数其最低位为第0位。

            sc_uint类型的操作符集与sc_int一致。sc_uint类型的操作数可以通过赋值语句转换成sc_int类型的操作数,反之亦然。

            当把整型数赋值给无符号类型的操作数时,以2的补码形式表示的整数值被解释为无符号的数字。当把无符号的整型数赋给有符号的操作数时,无符号的数被扩展为64位的无符号数,然后截取多余高位得到有符号的值。

    3.9 任意精度的整数类型

            sc_bigint, sc_biguint类型是一种可指定任意位宽、精度可达任意高的整数类型。若精度要求高于64位,则必须使用该类型;若精度要求等于或者低于64位,则可以使用sc_int整数类型(可使仿真运行较快)。

    sc_bigint, sc_biguint类型可与其它C++类型兼容,可使用赋值操作符将其赋值给其它整数类型的目标。to_string()方法可用于以不同格式打印这种类型的值。

    1. sc_bigint <100> comp, big_reg //声明两个整数类型,变量,其精度位宽为100位
    2. sc_bigint<70> con_sig; //一个70位的整数
    3. sc_biguint<70> fef_rw; //一个70位的无符号整数
    4. big_reg = 1000248;
    5. cout<<" The value of big_reg is "<< big_reg.to_string(SC_HEX) <

    3.10 判断类型

            判断类型(resolved types)可用于由多个源驱动的信号或者端口的建模。在这种建模过程中,必须对两个或者多个驱动器对信号或者端口的结果作出判断。SystemC为端口和信号提供了判断类型的逻辑标量和向量。例如:

    1. sc_out_resolved //逻辑标量端口的判断类型
    2. sc_inout_resolved
    3. sc_out_rc //逻辑向量端口的判断类型
    4. sc_inout_rv
    5. sc_signal_resolved //逻辑标量信号的判断类型
    6. sc_signal_rv //逻辑向量信号的判断类型

            每个包含对信号或端口赋值的过程都会生成一个对信号或端口的驱动器。所以,若同一个信号或端口由多个过程驱动,则必须对信号或端口的值作出判断。判断的规则按照下面的表格进行。

            诸如sc_out_rv 的判断类型,除了其附加的语义,即当有多个驱动器对该端口进行驱动时,端口的最终值将根据以上表格作出判断外,其余都类似于sc_out>类型。sc_lv类型的端口或信号不允许有多个驱动器,否则将会出现错误。例如:

    1. sc_signal 4> > mem_word; //不允许有多个驱动器(即不能在多个进程中赋值)
    2. sc_signal_rv <4> cycle_counter; //允许在多个进程中赋值(即允许多个驱动源)

    3.11 用户定义的数据类型

                    可以用枚举(enum)类型和结构(struct)类型来创建新的数据类型。信号可以被声明为这样的类型。但是在这种场合,必须提供一下四个附加的、对新数据类型进行操作的重载(over-loaded)函数,才可将这些函数用于新创建数据类型的信号。

    • 操作符“=”  赋值
    • 操作符“==” (逻辑等于)
    • 操作符“<<” (流输出)
    • sc_trace()

            考虑以下由用户定义的类型micro_bus和四个该类型定义的函数。

    //文件: micro_bus.h

    1. #include "systemc.h"
    2. const int ADDR_WIDTH = 16;
    3. const int DATA_WIDTH = 8;
    4. struct micro_bus{
    5. sc_uint address;
    6. sc_uint data;
    7. bool read, write;
    8. micro_bus& operator = (const micro_bus&);
    9. bool operator == (const micro_bus&) const;
    10. }
    11. inline micro_bus&
    12. micro_bus::operator = (const micro_bus& arg){
    13. address = arg.address;
    14. data = arg.data;
    15. read = arg.read;
    16. write = arg.write;
    17. return (*this);
    18. }
    19. inline bool micro_bus::operator == (const micro_bus& arg) const{
    20. return(
    21. (address == arg.address) &&
    22. (data == arg.data) &&
    23. (read == arg.read) &&
    24. (write == arg.write));
    25. }
    26. inline ostream&
    27. operator << (ostream& os, const micro_bus &arg){
    28. os <<"address= " << arg.address <<
    29. "data= " <" read= " <
    30. "write= "<
    31. return os;
    32. }
    33. inline void sc_trace(sc_trace_file *tf, const micro_bus& arg, const sc_string& name){
    34. sc_trace(tf, arg.address, name+".address");
    35. sc_trace(tf, arg.data, name+".data");
    36. sc_trace(tf, arg.read, name+".read");
    37. sc_trace(tf, arg.write, name+".write");
    38. }

            下面是两个使用micro_bus类型声明的信号。可以对这两个信号进行已定义的操作。例如:

    sc_signal  bus_a, bus_b;

    3.12 推荐的数据类型

            到目前为止,对绝大多数设计而言,采用bool和sc_uint这两种类型就足够了。下面是一些场景的推荐选取规则,可以帮助指导大家在编写SystemC RTL 模型时类型的选用。

    1. 对1位,使用bool数据类型。
    2. 对向量和无符号的算术运算,使用sc_uint 数据类型。
    3. 对有符号的算术运算,使用sc_int数据类型。
    4. 若向量的维数大于64位,则使用sc_bigint或根据相应情况使用sc_biguint。
    5. 对于循环控制变量,使用int数据类型或者任何其它C++整数类型。但不要依赖于该整数类型的维数来为你的设计建模。
    6. 只在四值逻辑信号场合使用sc_logic和sc_lv类型。
    7. 只在端口或信号存在多驱动器的场合,此时必须使用判断时才使用判断类型的数据。

  • 相关阅读:
    推荐几款最好用的MySQL开源客户端,建议收藏!
    题解:HDOJ 选择最佳路线
    2022债市波动分析
    Go字符串实战操作大全!
    无人出租赛道洗牌开启?这家公司为什么会黄?
    java计算机毕业设计在线学习跟踪系统前台源程序+mysql+系统+lw文档+远程调试
    【无标题】
    澳大利亚网络空间安全体系建设论析
    Java中for、foreach、stream区别和性能比较
    IT专业入门,高考假期预习指南
  • 原文地址:https://blog.csdn.net/u010420283/article/details/133241821