本章将详细的描述SystemC的数据类型,并介绍这些类型的数据可以进行哪些操作。比如值保持器(value holder)就是一种特殊的类型。在所有的类型中,最重要的是bool和sc_uint两种类型
值保持器有三种:
type variable_name1,variable_name2,...;
//for example
int mpy;
sc_signal signal_name1, signal_name2,...;
//for example
sc_signal mask;
sc_in input_name1,input_name2,...;
sc_out output_name1,output_name2,...;
sc_inout inout_name1,inout_name2,...;
//for example
sc_out > addi[6];
注意,和标准的C++一样,不能只用一条赋值语句完成对多维数组的赋值。必须逐个元素对数组赋值。
sc_signal[bool] mask[256][16];
for(i=0; i<256; i++)
for(j=0; j<16; j++)
mask[i][j]=false;
如下是SystemC RTL 支持的SystemC 数据类型和C++数据类型。C++的数据类型具体位宽和计算机操作系统平台有关。这些数据类型可以用来声明变量、信号和端口。
用sc_bit声明位类型。该类型的取值为‘0’或‘1’, ‘0’表示假,‘1’表示真。
在任何布尔类型逻辑操作或者赋值中,位类型操作数与bool类型操作数可以自由混合。
下面是位类型的操作符:
类型sc_bv定义了一个任意位宽的位向量,这是一个类型为sc_bit的向量,该向量的位宽在类型定义时被指定。该向量最右边的序号为0,即最小位。宽度W设置了向量的位宽,最高位为W-1位,一直到最低位0位。
下面是几个例子,注意在表示位宽的WIDTH的记号与最后那个尖括号“>”之间必须添加一个空格字符。
sc_bv<8> ctrl_bus;//声明ctrl_bus是一个8位的向量,位序号从7到0,ctrl_bus[0]是最低位
sc_out > mult_out; //声明了变量mult_out为位宽4的向量输出端口,位序号从3到0
sc_bv<4> mult;
ctrl_bus = "10110000";
ctrl_bus = "10011"; //不够8位,左边补0,即变成“00010011”
mult_out = "1011";
下面列出了支持位向量操作数的操作符和方法。
比较特别的操作是位选择操作符[], 拼接操作符(,),范围选择方法range()和六个缩减操作方法。
range()被用来获得向量的位范围。range()方法中的范围索引可以是递增的,也可以使递减的。
缩减“与”方法and_reduce()对向量的所有位进行逻辑“与”操作,然后返回1位的结果。其它几个缩减方法类似。
示例如下:
ctrl_bus[5] = '0';
ctrl_bus.range(0,3) = ctrl_bus.range(7,4);//ctrl_bus的第7位赋值给第0位,第6位赋值给第1位,第5位赋值给第2位,第4位赋值给第3位
mult = (ctrl_bus[0], ctrl_bus[0], ctrl_bus[0], ctrl_bus[1]);
ctrl_bus[0] = ctrl_bus.and_reduce();
ctrl_bus[1] = mult.or_reduce();
位操作符和range()方法只适用于变量,不能用于端口或者信号。若必须对端口或者信号进行位选择或者范围选择,则必须使用一个临时变量。
使用示例如下:
sc_signal > dval;
sc_in > addr;
sc_bv<4> var_dval;
sc_bv<8> var_addr;
sc_bit ready;
//读输入地址addr的第2位
var_addr = addr.read();
ready = var_addr[2];
//把‘011’赋予信号dval的若干位
var_dval = dval;
var_dval.range(0,2) = "011";
dval = var_dval;
对位向量类型不允许进行算术运算。为了支持这种运算,位向量类型的操作数可以先被赋予一个有符号的或者无符号的整型变量,进行必要的算术运算,然后将运算结果转回到原来的位向量。为了允许位向量和整型变量之间的转换,赋值可被重载。
sc_in > pha1;
sc_signal > pha2;
sc_uint<4> uint_pha1;
sc_uint<6> uint_pha2;
uint_pha1 = pha1;
uint_pha2 = pha2;
uint_pha2 = uint_pha2 - uint_pha1;
pha2 = uint_pha2;
声明局部变量时候,可以将其所有位的值一起初始化为‘1’,或者其它任意值。
//将所有位初始化为‘1’
sc_bv<8> all_ones('1);
sc_bv<4> dtack(true);
//将变量初始化为任意值
sc_bv<8> test_pattern ="01010101";
sc_bv<4> wbus = "0110";
逻辑类型用sc_logic声明。该类型共有四个值:
在使用赋值、相等和不相等操作符时,sc_logic类型的操作符可以与sc_bit类型的操作符自由混用
sc_logic pulse, trig;
sc_bit select;
sc_signal stack_end;
sc_inout load_stack;
stack_end = SC_LOGIC_Z; //赋高阻抗值
pulse != select; //sc_bit类型与sc_logic类型可以进行比较
select = trig; //sc_logic类型可以赋值给sc_bit类型,若trig是“X”或者“Z”将发出警告
load_stack = SC_LOGIC_X ; //给双向端口赋‘X’值
逻辑类型的操作数按位操作的行为可用下面的表格定义:
在某些场合,可以明确地给sc_logic类型赋值。也可以用to_bool()方法将sc_logic型的逻辑值转换为bool型逻辑值。
sc_logic('Z');
static_case ('Z');
trig = SC_LOGIC_Z; //和下面一句等价
trig = sc_logic('Z');
bool wrn;
sc_logic pena(SC_LOGIC_1);
wrn = pena.to_bool();
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"指定为十进制或者十六进制字符串。如下示例。
sc_lv<4> data_bus; //位宽为4的逻辑向量
sc_signal > counter_state; //8位的逻辑向量
sc_out > sensor;
data_bus = "0011";
data_bus = "0d14"; //指定为十进制数值
sensor = "10110XX011000ZZZ";
sc_lv<8> dtack_read = "0XFE"; //指定为十六进制数值
sc_lv<4> coh_rd ="0XA";
把数值赋予逻辑向量时候,若值的位宽与等号左边逻辑向量的位宽不符,根据数值的位宽大于或者小于逻辑向量的位宽,在数值的高位截断多余的高位或者添加“0”扩展数值。
局部变量可以在其声明期间,将所有位初始化为“Z”值或者“X”值。
sc_lv<8> allzs(SC_LOGIC_Z);
sc_lv<4> allxs(SC_LOGIC_X);
sc_lv<4> mbpc('0');
sc_lv<8> prog_ctr(SC_LOGIC_1);
sc_lv<4> as_byte(true);
支持逻辑向量操作数的操作符与支持位向量操作数的操作符完全一样。与位向量一样,逻辑向量的端口和信号也不能直接进行位选和部分位选,所以必须借助一个临时变量来完成所想要实现的赋值。
sc_out > bmask;
sc_lv<8> temp;
sc_logic rx_ok, tx_ok;
temp = bmask;
temp[4] = rx_ok;
temp[7] = tx_ok;
bmask = temp;
sc_signal > sabm;
sc_logic sel_bit;
sc_lv<4> q_temp;
q_temp = sabm;
sel_bit = q_temp[3];
不允许对逻辑向量类型进行算术运算。若想进行算术运算,必须首先把逻辑向量类型的操作数赋予有符号或者无符号的整型数,然后进行算术运算,再把运算结果返回到逻辑向量。
sc_lv<4> index1;
sc_lv<8> index2;
sc_int<4> int_index1;
sc_int<8> int_index2;
int_index1 = index1;
int_index2 = index2;
int_index2 += int_index1;
index2 = int_index2;
赋值被重载以允许逻辑向量与整型数之间互相转换。位向量和逻辑向量可以互相赋值。若在赋值期间,被赋予整型数或者位向量的值中包含“X”或“Z”,则结果是不确定的,会在运行时发出报警。
sc_uint<4> driver;
sc_int<8> q_array;
driver = data_bus; //给无符号整型数赋逻辑向量
q_array = data_bus; //若data_bus存在“X ”,"Z", 将在运行时发出报警,赋值结果不确定
sensor = q_array; /因为右式是符号值(逻辑向量无符号),所以q_array左边多余的位都用“0”补上
data_bus = driver; //给逻辑向量赋无符号整数值
int srw; //用to_int()方法将逻辑向量转换为整型数
sc_lv<8> crd_value;
src = data_bus.to_int();
driver.range(2,0) = crd_value.range(7,5).to_int();
q_array.range(7,2) = crd_value.range(1,6).to_int();
sc_int用来定义有符号的整数类型。这是一个有固定精度的整数类型,其最高精度为64位。整数类型的位宽可以明确指定。本类型被解释为有符号的整数类型,其值用2的补码形式表示。位宽为W的sc_int整数类型,其第W-1位为符号位,最低位为第0位。
所有按位操作符对整型量的操作都使用与该整型量等价的位向量实现。整型量的某一位可使用位选择操作符[]访问。整数类型的范围可使用range()方法访问。缩减操作符对整数类型对应的向量进行缩减运算产生1位的结果。concat()函数也能被用于进行拼接操作。
sc_int<4> sel_addr, inc_pc;
sc_int<8> opcode;
sc_int<12> sel_data;
#define N 7
sc_in > cpu_control[4];
sc_int<16> hr_reg_file[32];
opcode = sel_addr + inc_pc;
sel_data = -12;
opcode = sel_data << 2;
sel_addr = 6;
inc_pc = -5;
sel_addr = sel_addr ^ inc_pc;
sel_data = 100;
inc_pc = sel_data.range(3,0);
opcode.range(1,0) = (sel_data[6], sel_data[7]);
hr_reg_file[2] = concat(sel_data, sel_addr);
bool stop_clk;
bool start_clk;
stop_clk = inc_pc.and_reduce();
start_clk = hr_reg_file[15].xor_reduce();
sc_int类型与C++整型是兼容的,并且能互相交换使用。使用to_int()方法可以将位向量或者逻辑向量转换成有符号的整型值。
sc_bv<8> tic;
sc_int<4> itf;
sc_int<8> int_tic;
sc_int<4> int_itf;
int_tic = tic.to_int();
int_itf = itf.to_int();
若想以位的形式打印该有符号整型值,必须将该变量赋予位向量。如下:
cout << "Select address bus has "<< (sc_bv<4>)sel_addr <
to_string()方法可被用来打印出不同格式的整型值。可供选择的打印格式种类如下:
示例:
sc_int<8> rx_data =106;
sc_int<4> tx_buf = -11;
cout << "Default: rx_data = " << rx_data.to_string() <sc_bigint <100> comp, big_reg //声明两个整数类型,变量,其精度位宽为100位
sc_bigint<70> con_sig; //一个70位的整数
sc_biguint<70> fef_rw; //一个70位的无符号整数
big_reg = 1000248;
cout<<" The value of big_reg is "<< big_reg.to_string(SC_HEX) <
判断类型(resolved types)可用于由多个源驱动的信号或者端口的建模。在这种建模过程中,必须对两个或者多个驱动器对信号或者端口的结果作出判断。SystemC为端口和信号提供了判断类型的逻辑标量和向量。例如:
sc_out_resolved //逻辑标量端口的判断类型
sc_inout_resolved
sc_out_rc //逻辑向量端口的判断类型
sc_inout_rv
sc_signal_resolved //逻辑标量信号的判断类型
sc_signal_rv //逻辑向量信号的判断类型
每个包含对信号或端口赋值的过程都会生成一个对信号或端口的驱动器。所以,若同一个信号或端口由多个过程驱动,则必须对信号或端口的值作出判断。判断的规则按照下面的表格进行。
诸如sc_out_rv
sc_signal > mem_word; //不允许有多个驱动器(即不能在多个进程中赋值)
sc_signal_rv <4> cycle_counter; //允许在多个进程中赋值(即允许多个驱动源)
可以用枚举(enum)类型和结构(struct)类型来创建新的数据类型。信号可以被声明为这样的类型。但是在这种场合,必须提供一下四个附加的、对新数据类型进行操作的重载(over-loaded)函数,才可将这些函数用于新创建数据类型的信号。
考虑以下由用户定义的类型micro_bus和四个该类型定义的函数。
//文件: micro_bus.h
#include "systemc.h"
const int ADDR_WIDTH = 16;
const int DATA_WIDTH = 8;
struct micro_bus{
sc_uint address;
sc_uint data;
bool read, write;
micro_bus& operator = (const micro_bus&);
bool operator == (const micro_bus&) const;
}
inline micro_bus&
micro_bus::operator = (const micro_bus& arg){
address = arg.address;
data = arg.data;
read = arg.read;
write = arg.write;
return (*this);
}
inline bool micro_bus::operator == (const micro_bus& arg) const{
return(
(address == arg.address) &&
(data == arg.data) &&
(read == arg.read) &&
(write == arg.write));
}
inline ostream&
operator << (ostream& os, const micro_bus &arg){
os <<"address= " << arg.address <<
"data= " <
下面是两个使用micro_bus类型声明的信号。可以对这两个信号进行已定义的操作。例如:
sc_signal bus_a, bus_b;
到目前为止,对绝大多数设计而言,采用bool和sc_uint这两种类型就足够了。下面是一些场景的推荐选取规则,可以帮助指导大家在编写SystemC RTL 模型时类型的选用。