480 likes | 702 Views
SystemC 2.0.1. Tiago Salmito mahatma@natalnet.br. SystemC. Biblioteca de classes para C++. Suporta o co-design do hardware-software e a descrição da arquitetura de sistemas complexos que consistem de componentes de software e hardware.
E N D
SystemC 2.0.1 Tiago Salmito mahatma@natalnet.br
SystemC • Biblioteca de classes para C++. • Suporta o co-design do hardware-software e a descrição da arquitetura de sistemas complexos que consistem de componentes de software e hardware. • Serve para criar a "especificação executável" de um sistema.
Por que C? • C++ provê a abstração de controle e de dados necessários para descrição de sistemas. • A orientação a objeto e as macros do C++ nos permite estender a linguagem com classes, sem mudar a sua sintaxe.
Benefícios da "Especificação Executável" • Evitar inconsistências, erros e garantir a completude do sistema; • Assegurar uma interpretação não ambígua para a especificação do sistema. • Verificar a funcionalidade do sistema antes do inicio da sua implementação. • Criar modelos de performance do sistema, ou validar a performance do sistema. • Os casos de testes usados na EE podem ser usados no teste da implementação do sistema. • O exato comportamento do sistema pode ser validado usando-se o Waveform Tracing.
Definições • Módulos: São entidades hierárquicas onde podem conter outros módulos ou processos. • Processos: São usados para descrever as funcionalidades dos módulos. • Portas: Módulos possuem portas nas quais o conecta a outros módulos. Podem ser unidirecionais ou bidirecionais. • Sinais: São usados para se conectar portas (fios). Podem ser controlados por um (unresolved) ou vários (resolved) drivers. • Clocks: É um sinal especial usado para a sincronização do sistema durante a simulação.
SystemC 1.0 - Módulos • São os blocos de construção básicos no SystemC para particionar o design. • Eles encapsulam as representações dos dados internos e algoritmos de outros módulos. • São declarados: • Com a Macro: SC_MODULE(nome) { ... }; • Usando OO: struct nome : sc_module { ... }; • Um módulo pode conter diversos outros atributos como portas, sinais locais, dados locais, outros módulos, e construtores. • O construtor é declarado com a macro: SC_CTOR(nome_do_modulo) { ... }
Módulos: Portas • Passam dados de (ou para) processos de um módulo. • Podem ser declaradas de 3 maneiras: • Entrada - sc_in<tipo> nome; • Saída - sc_out<tipo> nome; • Entrada/Saída - sc_inout<tipo> nome; • Os tipos das portas podem ser qualquer tipo do C++, do SystemC ou tipos definidos pelo usuário. • Exemplo: Flip Flop D SC_MOLDULE(dff) { sc_in<bool> din; sc_in<bool> clock; sc_out<bool> dout; ... };
Módulos: Sinais • Representam os fios que interconectam os dispositivos de mesma hierarquia na implementação física do sistema. • Sinais carregam dados, e pode ser conectado a várias portas. • Exemplo: Filtro
"Positional mapping" SC_CTOR(filtro) { s1 = new sample ("s1"); (*s1)(q,s); c1 = new coeff ("c1"); (*c1)(c); m1 = new mult ("m1"); (*m1)(s,c,q); } "Named mapping" SC_CTOR(filtro) { s1 = new sample ("s1"); s1->din(q); s1->dout(s); c1 = new coeff ("c1"); c1->out(c); m1 = new mult ("m1"); m1->a(s); m1->b(c); m1->q(q); } Módulos: Maneiras de conectar sinais
Módulos: Armazenamento interno de dados #include "systemc.h" SC_MODULE(count) { sc_in<bool> load; sc_in<int> din; sc_in<bool> clock; sc_out<int> dout; int count_val; //armazenamento void count_up() { if (load) { count_val = din;} else { count_val++; } dout = count_val; } SC_CTOR(count) { SC_METHOD(count_up); sensitive_pos << clock; } }; • Para isso o designer precisa apenas declarar variáveis locais aos módulos. • Podem ser de qualquer tipo do C++, do SystemC ou definidos pelo usuário. • Exemplo:
O verdadeiro trabalho dos módulos são feitos em processos. Processos são funções registradas no kernel do SystemC e são sensíveis a sinais. São "executados" a qualquer troca de valor do sinal na qual o processo é sensível. Exemplo: #include "systemc.h" SC_MODULE(dff) { sc_in<bool> din; sc_in<bool> clock; sc_out<bool> dout; void doit() { dout = din; } SC_CTOR(dff) { SC_METHOD(doit); sensitive_pos << clock; } }; Módulos: Processos
É executado sempre que houver uma instanciação a um módulo. Define a estrutura de processos no kernel do SystemC e inicializa as variáveis internas. Exemplo: SC_MODULE(ram) { sc_in<int> addr; sc_in<int> datain; sc_in<bool> rwb; sc_out<int> dout; int memdata[64]; // memoria local int i; void ramread(); //processos void ramwrite(); SC_CTOR(ram){ SC_METHOD(ramread); sensitive << addr << rwb; SC_METHOD(ramwrite) sensitive << addr << datain << rwb; for (i=0; i++; i<64) { memdata[i] = 0; } } }; Módulos: Construtor
Módulos: Casos de teste • São feitos através de módulos que geram estímulos e módulos que checam se a saída é coerente com a pós-condição
Processos • São a unidade básica de execução no SystemC, são chamados para simular o comportamento do dispositivo ou sistema. • Existem 3 tipos de processos: • Métodos (SC_METHOD) • Threads (SC_THREAD) • Clocked Threads (SC_CTHREAD) • Não são hierárquicos. • Possuem uma lista de sensitividade e para estimular um processo deve-se ocorrer um evento.
Processos: Threads (SC_THREAD) • Executam indefinidamente e podem ser suspendidas e reativadas. • Podem executar o comando wait() para suspender. • São o são o caso mais geral de processo e podem ser usados para praticamente tudo. • Threads são implementados usando co-rotinas e a biblioteca do SystemC. • Exemplo: Sinal de transito. • SC_THREAD(sinal);
Processos: Clocked Threads (SC_CTHREADS) • São um caso especial de Threads. • São usados para criar maquinas de estado implícitas. • Um estado é definido entre dois comandos wait() ou wait_until(). • Só pode ser sensível ao clock. • Exemplo: Um controlador de barramento.
Diferenças entre threads e clocked threads • O processo Cthread especifica um objeto do tipo clock. • Cthread não possui lista de sensitividade. • A instrução wait_until só pode ser chamada através de um processo Cthread. • Um uso para o wait_until: wait_until(sensor.delayed() == true);
Processos: Watching • Watching só pode ser usado por um processo Cthread. • Processos Cthread são loops infinitos. • Watching é a maneira de se controlar o loop. • Exemplo: • SC_CTHREAD(gen_data, clk.pos()); • watching(reset.delayed() == true);
Portas e Sinais: • Para ler sinais das portas: • Use os métodos read() ou write() das portas ou o operador de atribuição. • -Se precisar fazer conversões de tipo use os métodos read() e write();
Portas e Sinais: Resolved Logic Vectors • São usados para sinais que possuem mais de um driver. • Declarando a porta: sc_inout_rv<n> x; • Declarando o sinal: sc_signal_rv<n> x; • Exemplo:
Conectando sinais e portas • Para conectar uma porta a um sinal eles devem ser do mesmo tipo. • Se for entre portas de mesmo nível de abstração: • Deve-se usar sinais. • Usa-se os métodos de “positional” ou “named mapping” • Se for entre portas de níveis diferente. • Não necessita de sinal (porta a porta). • Usa-se o método “named mapping”
Clocks • É um objeto especial do SystemC. • Para criar um sinal de clock: • sc_clock clock(“nome”, 20, 0.5, 0, true); • Para se acessar o sinal de clock usa-se o método clock.signal();
Tipos de dados do SystemC • sc_bit –2 value single bit type • sc_bit v=‘1’; sc_bit f=‘0’; • sc_logic – 4 value single bit type • sc_logic l=‘Z’; l=‘X’; l=
Tipos de dados do SystemC • sc_int – 1 to 64 bit signed integer type • sc_int<40> int_de_40bits; • sc_uint – 1 to 64 bit unsigned integer type • sc_uint<22> uint_de_22bits; • sc_bigint – arbitrary sized signed integer type • sc_bigint<128> b; • sc_biguint – arbitrary sized unsigned integer type
Tipos de dados do SystemC • sc_bv – arbitrary sized 2 value vector type • sc_bv<10> vetor_de_10_bits=“1110110010”; • sc_lv – arbitrary sized 4 value vector type • sc_lv<70> vetor_de_3_bits=“ZX01”;
Tipos de dados do SystemC • Tipos definidos pelo usuário: • Exemplo: struct tipo_pacote { long info; int seq; int reenvios; inline bool operator == (const tipo_pacote& rhs) const { return (rhs.info == info && rhs.seq == seq && rhs.reenvios == reenvios); } };
Simulação • A instanciação dos módulos é feita na rotina sc_main(); • Após instanciar e conectar os módulos aos sinais, é feita uma chamada a função sc_start(double); • A simulação pode ser parada, através da função sc_stop(); • Pode-se saber o tempo atual de simulação através da função double sc_simulation_time(); • Para ajudar no debug, todos as variáveis, portas e sinais podem ter seus sinais lidos e impressos.
Simulação avançada • O SystemC te dá a opção de controlar o controle de sinais assincronamente. • Criar seu próprio clock. • Criar sinais assíncronos para teste do sistema. • É feito através das performativas sc_initialize() e sc_cycle(); • Exemplo: • sc_signal<bool> clock; ...; • sc_initialize(); • for (int i = 0; i <= 200; i++) • clock = 1; sc_cycle(10); • clock = 0; sc_cycle(10); • } • Equivale à: • sc_clock clk(“meu clock”, 20, 0.5); • sc_start(200);
Traçando Formas de Ondas • SystemC pode criar arquivos nos formatos: • VCD (Value Change Dump) • ASCII WIF (Waveform Intermediate Format) • ou ISDB (Integrated Signal Data Base) • As formas de onda geradas podem ser visualizadas com qualquer software que suporte esses formatos. • Podem ser traçadas: • Somente variáveis que estão no escopo durante toda a simulação. • Sinais escalares, vetores ou de tipos agregados.
Traçando Formas de Ondas • Criando arquivos: • sc_trace_file * my_trace_file; • my_trace_file = sc_create_vcd_trace_file(“my_trace”); • Fechando arquivos • sc_close_vcd_trace_file(my_trace_file); • Traçando variáveis e sinais escalares: • sc_trace(my_trace_file, var, “Variável”); • O primeiro argumento e um arquivo do tipo sc_trace_file. • O segundo argumento é uma referencia a variável a ser traçada. • O terceiro é uma string de referência.
Traçando Formas de Ondas • Exemplo: sc_signal<int> a; float b; sc_trace(trace_file, a, “MyA”); sc_trace(trace_file, b, “B”); • Traçando variáveis de tipo agregado: struct bus { unsigned end; bool read_write; unsigned dado; }; void sc_trace(sc_trace_file *tf, const bus& v, const sc_string& NAME) { sc_trace(tf, v.end, NAME + “.end”); sc_trace(tf, v.read_write, NAME + “.rw”); sc_trace(tf, v.dado, NAME + “.dado”); }
Traçando Formas de Ondas • Traçando vetores: void sc_trace(sc_trace_file *tf, sc_signal<int> *v, const sc_string& NAME, int len) { char stbuf[20]; for (int i = 0; i< len; i++) { sprintf(stbuf, “[%d]”, i); sc_trace(tf, v[i], NAME + stbuf); } }
Pequeno Exemplo: Módulos #include <systemc.h> SC_MODULE(produtor) { sc_out<int> dout; sc_in<bool> clock; int dado; void produz() { dout = dado++; } SC_CTOR(produtor) { SC_METHOD(produz); sensitive_pos << clock; dado=0; } }; SC_MODULE(consumidor) { sc_in<int> din; void consome() { int tmp=din; printf(“Dado = %d”,tmp); } SC_CTOR(consumidor) { SC_METHOD(consome); sensitive << din; } };
Pequeno Exemplo: Juntando módulos #include “modulos.h” int sc_main(int argdc, char ** argc) { sc_signal<int> barramento; sc_clock clk(“clock”, 20); produtor p(“produtor”); p.dout(barramento); p.clock(clk); consumidor c(“consumidor”); c << barramento; sc_trace_file * tf=sc_create_vcd_trace_file”(“produtor_consumidor”); sc_trace(tf, barramento, “Barramento"); sc_trace(tf, clk.signal(), “Clock"); sc_start(-1); }
Estendendo o SystemC – SystemC 2.0 • A descrição estrutural do SystemC é suficiente para modelar sistemas (portas e módulos). • Porem a comunicação e a sincronização não é geral o suficiente para modelar em todos os níveis de sistema. • O SystemC 2.0 introduz novos conceitos para preencher essa lacuna. • Canais • Interfaces • Eventos
Tempo relativo vs Tempo absoluto • Definição do clock: • SystemC 1.0: sc_clock clk( “clk”, 20 ); • SystemC 2.0: sc_clock clk( “clk”, 20, SC_NS ); ou sc_time t1( 20, SC_NS ); sc_clock clk( “clk”, t1 ); • SC_FS – femtoseconds • SC_PS – picoseconds • SC_NS – nanoseconds • SC_US – microseconds • SC_MS – milliseconds • SC_SEC – seconds Para se setar a unidade de tempo: sc_set_default_time_unit( 1, SC_PS ); (deve ser uma potência de 10)
Eventos - SystemC 2.0 • Processos são sensíveis a eventos • O caso do SystemC 1.0 (portas) são um caso especial. • Wait() agora recebe o evento a ser esperado (lista de sensibilidade dinâmica) • wait( e1 | e2 | e3 ); • wait( 200, SC_NS ); • wait( 200, SC_NS, e1 | e2 | e3 ); • wait(); //systemc 1.0 • sc_event my_event; • my_event.notify(); //notifica agora • my_event.notify( 10, SC_NS ); //notifica em 10 nanosegundos • my_event.notify( SC_ZERO_TIME ); //proximo delta-cycle
Interfaces – SystemC 2.0 • Definem métodos a serem implementados em um canal: template <class T> class sc_read_if : virtual public sc_interface { public: virtual const T& read() const = 0; }; • Métodos de sc_interface: • void register_port( sc_port_base&, const char* ) • Usado para verificação quando se conecta uma porta a essa interface. • const sc_event& default_event() • Usado para a sensibilidade estática do SystemC 1.0.
Portas – SystemC 2.0 • Possuem interfaces. • Exemplo: sc_in<tipo> possui a interface sc_read_if. • Exemplo de porta: template <class T> class meu_sc_in: public sc_port<sc_read_if<T> > { public: operator const T&() const { return (*this)->read(); } }; • Uma porta deve possuir pelo menos uma interface.
Canais – SystemC 2.0 • Um canal implementa uma ou mais interfaces: • Exemplo de canal: sc_signal é um canal que implementa as interfaces if_read e if_write. • Porém o sc_signal só pode possuir uma porta de saída conectada. (essa verificação é feita no método register_port()). • Exemplo: • Canal sc_fifo: • Só pode ter uma porta de entrada e uma de saída (nenhuma bidirecional).
Canais: sc_fifo • O método register_port verifica as condições de conexão dos canais. template <class T> void sc_fifo<T>::register_port( sc_port_base& port,const char* if_typename ) { sc_string nm( if_typename ); if( nm == typeid( inout_interface<T> ).name() ) { error( “cannot connect to a bi-directional port” ); } else if( nm == typeid( in_interface<T> ).name() ) { if( no input port registered so far ) input_port = port; else error( “cannot connect to multiple inputs” ); } else { // nm == typeid( out_interface<T> ).name() if( no output port registered so far ) output_port = port; else error( “cannot connect to multiple outputs” ); } }
Implementando Canais • O método update() de um canal irá ser chamado na fase de atualização dos canais. • É a hora de se determinar se há algum evento a ser gerado. • Exemplo de update: sc_signal virtual void update() { if( !( m_new_val == m_cur_val ) ) { m_cur_val = m_new_val; m_value_changed_event.notify( SC_ZERO_TIME ); }
Pequeno Exemplo: Canais – sc_signal template <class T> class sc_signal: public sc_prim_channel, public sc_signal_inout_if<T> { public: sc_signal() : m_output( 0 ), m_driver( 0 ) {} virtual void register_port( sc_port_base& port, const char* if_typename ) { sc_string nm( if_typename ); if( nm == typeid( sc_signal_inout_if<T> ).name() ) { // a write or read/write port; only one can be connected if( m_output != 0 )error( “more than one write port\n” ); m_output = &port; } // any number of read ports can be connected: no check needed } virtual const T& read() const { return m_cur_val; } virtual const sc_event& default_event() const{ return m_value_changed_event; } virtual void write( const T& value ) { // dynamic design rule checking if( current_process() != m_driver ) { if( m_driver == 0 ) m_driver = current_process(); else error( “more than one driver\n” ); } m_new_val = value; if( m_new_val != m_cur_val ) request_update(); }
Pequeno Exemplo: Canais – sc_signal protected: virtual void update() { if( !( m_new_val == m_cur_val ) ) { m_cur_val = m_new_val; m_value_changed_event.notify(SC_ZERO_TIME ); //notifica no próximo delta-cycle } } sc_port_base* m_output; // for static design rule checking sc_process_b* m_driver; // for dynamic design rule checking T m_cur_val; T m_new_val; sc_event m_value_changed_event;