Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
180 views

Assignments System Programming in C

This document describes Assignment 1 of a course on System Programming in C. The assignment involves developing a program to simulate the logic of a simple circuit. Students are expected to: - Write functions, use loops and arrays, pass parameters - Read keyboard input and write output to the screen - Simulate a given circuit with 4 inputs and 2 outputs made of 8 gates The program must support two modes - taking a user-defined input and exhaustive simulation of all inputs. It will output the corresponding output vectors based on the gate truth tables provided. The assignment aims to practice basic C concepts taught in the course.

Uploaded by

akrimetch
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
180 views

Assignments System Programming in C

This document describes Assignment 1 of a course on System Programming in C. The assignment involves developing a program to simulate the logic of a simple circuit. Students are expected to: - Write functions, use loops and arrays, pass parameters - Read keyboard input and write output to the screen - Simulate a given circuit with 4 inputs and 2 outputs made of 8 gates The program must support two modes - taking a user-defined input and exhaustive simulation of all inputs. It will output the corresponding output vectors based on the gate truth tables provided. The assignment aims to practice basic C concepts taught in the course.

Uploaded by

akrimetch
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 23

System Programming in C ET4174

Assignments 2012-2013

Computer Engineering Lab Delft University of Technology The Netherlands


Ir. M. Taouil Dr. Ir. S. Hamdioui

Table of contents

1 2

Introduction................................................................................................................................................3 Assignment 1: Logic Circuit Simulation ..................................................................................................5 2.1 Required knowledge .........................................................................................................................5 2.2 Objectives ........................................................................................................................................5 2.3 Problem statement .............................................................................................................................5 2.4 The assignment .................................................................................................................................6 3 Assignment 2: Read Netlist ......................................................................................................................8 3.1 Required knowledge .........................................................................................................................8 3.2 Objectives..........................................................................................................................................8 3.3 Problem statement .............................................................................................................................8 3.4 The assignment .................................................................................................................................9 4 Assignment 3: Netlist Simulation ...........................................................................................................15 4.1 Required knowledge .......................................................................................................................15 4.2 Objectives........................................................................................................................................15 4.3 Problem statement ...........................................................................................................................15 4.4 The assignment ...............................................................................................................................17

1 Introduction
The C programming language is a general-purpose programming language which features economy of expression, modern control flow and data structures, and a rich set of operators. C is not a very high level language, nor a big one, and is not specialized to any particular application. However, its absence of restrictions and its generality make it more convenient and effective for many tasks like operating systems, compilers, etc. System C programming course, ET4174, provides the background needed to understand, create, modify, and extend an existing program. The lab assignments provide the opportunity for students to practice with the concepts learned, as the best way to become a good programmer is to practice and write programs. Study Goals The main goals of the course are: Write a program in C to solve specific problems. Understand and explain a program written in C. Modify an existing program in C to change certain functionalities. Extend an existing program in C to enable additional functionalities. Become proficient in the C programming language. Course material: Brian W. Kernighan and Dennis M. Ritchie, The C Programming Language, 2nd edition. Other materials can be find on the internet; examples are http://www.iu.hio.no/~mark/CTutorial/CTutorial.html and http://randu.org/tutorials/c/. Assignments There are in total three assignments, each having a focus on certain concepts as given in the table below. You are allowed to do you assignments either at home or at the university. You are only allowed to use one of the following two compilers: GCC. Some useful links are: http://www.network-theory.co.uk/docs/gccintro/gccintro_9.html http://oreilly.com/linux/command-directory/cmd.csp?path=g/gcc Visual Studio. Some useful links are: http://ceng230.ceng.metu.edu.tr/Compilers/ms-cpp.html http://www.ehow.com/how_5179139_use-microsoft-visual-c.html http://foddex.net/tutorials/65 Nevertheless, we strongly recommend you to use the GCC compiler.

AS
1

Required knowledge
Types, operators and expressions Control flow; functions; loops Arrays; I/O: Keyboard input ASCII characters Call by reference, call by value Using #define directives Required knowledge of AS1 Dynamic memory allocation, reallocation and de-allocation Call by reference, call by value Arrays, pointers and arrays of pointers Strings; Text files I/O Required knowledge of AS2 Linked lists data structures Timing in gcc

Objectives
Write simple functions; Use for and while loops Read keyboard inputs; Use arrays Pass parameters to functions (value vs. address) Write output to screen

Chapters
Chapter 1 Chapter 2 Chapter 3 Chapter 4

Use pointers, arrays and structures Pass parameters to functions (value vs. address) Use character string and std functions Learn how to read/write from text files Use strings and string.h library Use dynamic memory allocation and reallocation Learn how to use hash tables Similar to objective of AS2 Use linked lists Compute the execution time

Chapter 5 Chapter 6 Chapter 7 Appendix B3

All

The objective of the assignments, all together, is to create a simple circuit simulator. The simulator reads in a netlist (i.e., a circuit description in terms of input, output, gates and flip-flops) in Verilog format and subsequently is simulated by applying input patterns on its input. In order to realize such a simulator, three assignments must be completed:

Assignment 1: Logic Circuit Simulator. The objectives of the first assignment is to get familiar with the C-language and to get familiar with its basic concepts such as program structure, types, operators, expressions, control flow, functions, , loops, arrays; I/O keyboard input, ASCII characters, etc. In this assignment input test patterns for a hardcoded circuit are simulated. This assignment is relative easy as compared to the other assignments. Assignment 2: Read Netlist. The purpose of this assignment is to get familiar with more advanced concepts of C, e.g., the usage of pointers, memory management, and reading from and writing to files, using multiple source files etc. In this assignment, your task is to read a Netlist in Verilog format. Assignment 3: Netlist Simulation. This assignment reuses functions from assignments 1 and 2 and aims at finalizing the simulator by applying input stimuli to the circuit. In this assignment you get more experienced with the advanced C concepts and learn also to work with new concepts like linked lists and timing functions.

Assessment There will be no written exam. There are three assignments; each assignment and its associated report (Rep) will be given a mark. It is estimated that the total amount of time that all these assignment will consume (preparation and implementation)is about 56 hours. The final mark is calculated using the following formula (ASX = Assignment X): Final_Mark = (1+Rep+AS1+3*AS2+4*AS3)/ 10; Rep denotes the average mark of the three reports of the assignments. NOTE: You should deliver a report with each assignment! The main criteria for grading the assignments are: Correctness of the code (50%): Memory allocation and deallocation (20%) Code efficiency and optimisation (15%) Program structures and code readability (15%). Some tips are: (a) layout/indenting- use tabs rather than adding extra spaces using space bar, (b) use comments before every function and/or procedure, (c) use appropriate naming for constants, variables, and functions, etc.

2 Assignment 1: Logic Circuit Simulation


2.1 Required knowledge

In order to complete this assignment, understanding of the following topics is required: Types, operators and expressions Control flow; functions; loops Arrays I/O: Keyboard input ASCII characters Call by reference, call by value 2.2 Objectives

After this assignment the students should be able to: Write simple functions Pass parameters to functions (value vs. address) Use for and while loops Use arrays Read keyboard inputs Write output to screen 2.3 Problem statement Consider the circuit shown in Figure 2.1. The circuit has 4 inputs and 2 outputs, and consists of 8 gates. Your task is to develop a program to simulate the correctness of the circuit by providing input stimulus and reading the outputs. The program has to operate in two possible modes: (a) User defined input stimulus, and (b) Exhaustive simulation. User defined input stimulus: in this case, the user defines an input vector from a command line, and the program has to produce the corresponding output vector. For example, if the input stimulus provided is 1011, then the program will produce the output vector 11 on the screen. In the input and output vectors, the LSB bit corresponds to the first element of the vector. Thus, the value 0 in 1110 represents the LSB=inputs[0], and 1 the MSB=inputs[3]. Exhaustive simulation: In this case, all the possible input combinations are simulated and all the associated output vectors will be displayed on the screen; e.g. o 0000 will produce 10 o 0001 will produce 10 o 0010 will produce 01 o Etc.

Figure 2.1 - Logic circuit

Figure 2.2 shows the truth table of each gate; i.e., the relation between their input and their outputs. In this figure, the symbol (i.e., dont care) is used to denote any of the inputs 0, 1 or X.

Figure 2.2 - Truth table of logic gates In the assignment, it is not allowed to use global variables; it is also not allowed to change the provided declarations. Use meaningful variables, constants, etc. and use enough comments to make your code easy to read and understand. 2.4 The assignment A. In digital circuits each input/output can take either the value 0 or 1. For this simulation, we will assume that every signal can take on three possible values: 0, 1 or unknown, which will be presented by X. These will be defined as identifiers. Define the following identifiers using the #define directive: LOGIC_0 as 0 LOGIC_1 as 1 LOGIC_X as 2 Note that the values 0, 1, and 2 here can in general be any values. B. Each digital circuit consists of different types of gates such as AND, NAND, OR, NOR, etc. For example, the circuit of Figure 1 consists of 8 gates classified in seven different types: two NOT gates, one AND gate, one NAND gate, one NOR gate, one XOR gate, one OR gate and one XNOR gate. In order to simulate the circuit, the function/ truth table of each of the gates has to be defined. Implement the following seven gate functions according to the truth tables in Figure 2.2. 1. 2. 3. 4. 5. 6. 7. int not_gate(int a) int or_gate (int a, int b) int nor_gate (int a, int b) int and_gate (int a, int b) int nand_gate (int a, int b) int xor_gate (int a, int b) int xnor_gate (int a, int b)

Note: Each input and output can be either 0 (LOGIC_0), 1 (LOGIC_1) or X (LOGIC_X). Hint: For a compact code, reuse functions as much as possible; for example, nand_gate (a, b) can be realized with not_gate(a) and and_gate(a, b). C. By using the above primitive functions we can now define the function logic_circuit for the circuit of Figure 1. The function has as arguments the array inputs and produces the corresponding array outputs. For example, if the input vector/array is 1011, then the corresponding output vector/array is 11. Implement the following function: void logic_circuit(int inputs[4], int outputs[2]) Hint: Use all the functions implemented in Task B. D. One should be able to display both the input vector as well as the output vector of the simulated logic circuit on the screen. For this purpose, implement the following function:

void print_vector(int vect[], int size) The argument vect consists of the array of bits to be printed and the argument size contains the array length. E.g., if vect contains {LOGIC_1, LOGIC_X, LOGIC_0}, then the string 1X0 will be produced on the screen. Hint: create an additional function char logic_to_char(int val) that converts the integer val to the appropriate character. For example, if val contains LOGIC_X, then the char X must be returned. E. As already mentioned, the to-be-developed simulator in this assignment has to operate in two different modes. One of them is user defined input stimulus. In this mode, the user will define a single input vector stimulus and the program will produce the corresponding output vector. Implement the function: void user_sim() The function should: Read a single input vector from the command line using scanf. For example, the input string 1X00 will result in the following input values: input[3]=1, input[2]=X, input[1]=0, input[0]=0. Check the validity of the input vector; i.e., each bit in the input vector is either 0, 1, or X. Check that the length of the input vector is correct. Evaluate a single vector and subsequently print the output vector. In case of an invalid input, the user should be informed about the nature of the error. Hint: create a function int char_to_logic(char val) that converts the character val to either LOGIC_0, LOGIC_1 or LOGIC_X. In case of invalid input, return the value -1. F. The second mode is the exhaustive simulation; in this case, all possible input combinations are simulated and all associated output vectors will be displayed on the screen. For this mode, we will assume the input vector to consists of only 0s and 1s. Implement the function: void exhaustive_sim() The output on the screen should look like the following: 0000 -> 10 0001 -> .. 0010 -> .. 0011 -> .. 1111 -> .. Print on the dots your output values. G. Finally, the two operational modes are combined in the function main(). Implement the main() function in such a way that it provides the user with a selection menu with 3 options as follows: Please specify your choice: 1 manual simulation 2 exhaustive simulation 3 exit >> The program should terminate only after the user has specified to exit. Hint: use a while loop

3 Assignment 2: Read Netlist


3.1 Required knowledge

In order to complete this assignment, understanding of the following topics is required: Deep understanding of Assignment 1 Dynamic memory allocation, de-allocation and reallocation Types, operators and expressions Call by reference, call by value Arrays, pointers, and arrays of pointers Strings Text files I/O 3.2 Objectives

After this assignment students should be able to: Use pointers and arrays Pass parameters to functions (value vs. address) Use loop statements Get familiar with dynamic memory allocation Know how to read/write from text files Use character, strings and string.h library Make use of the advantage of pointers flexibility Use structures in combination with pointers Use dynamic memory allocation and reallocation Use hash tables Use function-like macros 3.3 Problem statement

In the previous assignment a fixed circuit was used for simulation. In this assignment, your task is to read a circuit description (from a file) appropriately in such way that it can be used for circuit simulation in Assignment 3. The circuit description is given in a Verilog netlist format; the Verilog netlist is nothing else than a text file which describes the circuit in terms of wires, gates and the way they are connected.To verify the correctness of the reading process, the read netlist has to be re-generated and compared to the original netlist. The netlist consists typically of 4 types of statements. 1. # . Lines starting with the # symbol are comments and have to be ignored 2. INPUT(LBL) This defines an input wire of the circuit with the name/label LBL 3. OUTPUT(LBL) This defines an output wire of the circuit with label LBL 4. LBLx = <GATE>(LBL1, LBL2, LBL3, ) This defines a gate which inputs are connected to wires with the labels LBL1, LBL2, LBL3, etc and its output to the wire with label LBLx. For example an 5-input and-gate could be defined by: wout = AND(w1, w2, w3, w4, w5) Statements are assumed to be case insensitive. For example, INPUT(l1) and input(l1) are equivalent. However, wire labels are assumed to be case sensitive. Usually, the netlist contains the definitions in the following order: INPUT, OUTPUT, and <GATE>. However, no specific order exists within the gate statements, i.e., there is no order which specifies which gates are defined first. You can download and inspect several Verilog netlist examples from the ISCAS 85 benchmark at: http://www.pld.ttu.ee/~maksim/benchmarks/iscas85/bench/

On blackboard we have uploaded three benchmarks for you to use: 1. assignment1.bench: This contains a relative small netlist. The netlist describes the circuit depicted in Figure 2.1 of Assignment 1 2. c432.bench: This contains a medium sized netlist 3. c6288.bench: This contains a relative large netlist 3.4 The assignment

Before you start your assignment, you have to be aware about the following: 1. You start in this assignment with four C files. You can download them from blackboard. File Content hash.h Subroutines, variables, and other identifiers belonging to the hash table hash.c Implementation of the hash table netlist.h Subroutines, variables, and other identifiers belonging to this assignment netlist.c Implementation of this assignment Note that in the C-language only 1 main() function exists. 2. People that use gcc are provided with a basic Makefile in order to compile these files. This can be downloaded from blackboard. The Makefile can be called from the command line in two ways: Make clean this removes all temporary files and previous object codes Make app this compiles the code and generate an executable with the name app. In case you dont want to use the make file, you can compile the files yourself from command line by: >> gcc hash.c netlist.c This will create an executable ./a.out. In case you want to specify your own application name you can use the o switch of gcc: >> gcc hash.c netlist.c o application_name 3. In Visual Studio you have to create your own project and subsequently add the four files to this project. Make sure to add the source and header files on the proper places. 4. In the assignment, it is not allowed to use global variables; it is also not allowed to change the provided declarations. Use meaningful variables, constants, etc. and use enough comments to make your code easy to read and understand. 5. Include the assignment report when you submit your final code.

The assignment is divided into different tasks; each of them are described next. Mare sure to report on each of the tasks in your Assignment report. A. We consider a circuit to be composed of two different types of components: gates and wires. A gate has a variable number of inputs and only one output, while a wire component has only a single input and a variable number of output branches. Figure 3.1 shows an example of how these components are connected; the first output branch of the wire labelled g126 is connected to an and-gate labelled ANDg140, while the second output of the wire is unconnected. The gate ANDg140 has three inputs, (from which only one is connected) and one output connected to the wire g140. The latter has 4 output branches (all unconnected in the graph). Read and understand carefully the component as they are depicted in Figure 3.2. The code of figures 3.2 is included in the file netlist.h.

Figure 3.1 - Relation between circuit components


enum gate_type{Not, And, Or, Xor, Xnor, Nand, Nor}; enum circuit_type{Gate, Wire}; typedef struct s_wire{ struct circuit_item *in; int no_outputs; struct circuit_item **out; } s_wire;

//array of pointers of size out_connections

typedef struct s_gate{ enum gate_type type; int no_inputs; struct circuit_item **in; struct circuit_item *out; } s_gate; typedef struct circuit_item{ char name[50]; enum circuit_type type; union{ s_wire circuit_wire; s_gate circuit_gate; } cir_type; } circuit_item;

// array of pointers of size no_inputs

Figure 3.2 - Structures of circuit components

10

To externally interface with the circuit, two arrays will be used; each array contains pointers to either input wires or output wires of the circuit. This interface is presented by a structure s_interface given in the next figure. The code of the figure is included in the file netlist.h.
// s_interface contains arrays of pointers to the inputs and outputs of the circuit typedef struct s_interface{ int no_inputs; circuit_item **inputs; int no_outputs; circuit_item **outputs; } s_interface;

Figure 3.3 - Structure of external interface NOTE: In this task you need to understand the structures of figures 3.2 and 3.3, and provide necessary comments to make the netlist.h file easy readable. B. The internal part of the circuit (i.e., gates and wire connections) has to be accessed through a hash table. Read Section 6.6 of the book The C Programming Language that explains how hash tables work. Study the following functions in hash.h and hash.c carefully. You are not allowed to modify any of these functions node** create_hash_table(int size) unsigned djb_hash(char *key, int len) circuit_item * get_circuit_item(node *table[], int size, char *key ) int find(node *table[], int size, char *key ) int insert(node *table[], int size, char *key, circuit_item *net)

For each function above, add the necessary comments to the files and explain their functionality. For the time being, ignore the function clean_hash_table(node *table[], int size); this will be addressed later. The remaining tasks deals with the implementation of functions to be included in the files netlist.c and/or netlist.h, unless otherwise specified. C. The netlist will be read from a file. Each file consists of several lines. This task deals with reading a single line. Implement the function: void read_line(FILE *fp, char line[]) This function reads a line from the file fp and stores its content in the string line. Assume that the length of string line is large enough; e.g., 200. Hence, there is no need to use memory allocation. For example: char buff[200]; File *fp; readline(fp, buff); D. Next, three functions are used to identify the type of statement a line contains; this type can be either an INPUT, OUPUT, or <GATE>. Implement the following functions: int isInput(char *line) int isOutput(char *line) int isGate(char *line) Each function returns a 1 in case of a hit and 0 otherwise.

Hint: use the function strstr (see appendix B3 of the book) to look for certain characters in strings. 11

E. Before interpreting the netlist, we need to determine the number of inputs and outputs the circuit consists of. This information is used later to allocate proper memory for the interface arrays presented by s_interface stucture. Implement the following function: void netlist_statistics(FILE *fp, int *no_inputs, int *no_outputs) This function reads the complete input file to determine the total number of inputs and outputs, which are stored in no_inputs and no_outputs respectively. Hint: Use the functions implemented in Task C and D. F. Given a line in an input file, one has to identify the type of gate it may contain; e.g., the following two lines: G432 = NOT( II692) G359 = nand( G6, G31) contain a Not and a Nand gate; see also enum gatetype in netlist.h. Implement the function: enum gate_type determine_gate_type(char *str) The function returns the gate type as defined by enum gate_type (e.g, And, Nor, etc.) based on the content of string str ; the string is case-insensitive and contains the gate type (e.g. not or NOT). In the above example, the function will return Not and Nand; see also netlist.h.

Hint: Use the function strcmp() to compare 2 strings. You might want to use the functions
toupper() and tolower() prior to compare the strings. G. Gate statements in the input file consists of wires (single or multiple inputs and one single output). When such a statement is read, both gates and wires are stored in a hash table. Each time a new gate statement is read, one has to check if the wires within this statement already exist in the table or not. In case it exist, the wire itself is returned; otherwise, a new wire is added to the table. Implement the function performing such a task: circuit_item *get_or_insert_wire_item(node *table[], int table_size, char *str) This function returns a pointer to a circuit_item of type Wire with the name str. In case no wire exists with this name in the hash table, a new circuit_item of type Wire has to be created and added to the table; and the members of the structure (i.e., struct s_wire) should be initialized to 0 or NULL. Hint: Use the function circuit_item *get_circuit_item (node *table[], int size, char *key ) defined in hash.h. H. As mentioned in the problem statement, the netlist consist of four types of statements. Based on the type of statement, different actions have to be taken. The non-commenting statements are not considered, while the remaining statements (i.e, input, output and gate statements) are handled in this function. First, implement the following function that handles INPUT statements: void input_statement (char *line, circuit_item *inputs[], int *wr_index, node *table[], int table_size ) This function contains an INPUT statement in the string line (e.g. INPUT(LBL)), e.g., INPUT(g1). Create and insert a new circuit_item with the name LBL of type Wire and add this to the hash table. Subsequently, store a pointer to it on index wr_index of the array inputs[]. Use for the hash key the name LBL as well. Finally, increment the value of wr_index to prepare for the next INPUT statement. Hint: Use the function implemented in Task G to create and add wires to the hash table.

12

Next, implement the following function that handles OUTPUT statements:


void output_statement(char *line, circuit_item *outputs[], int *wr_index, node *table[], int table_size )

This function contains an OUTPUT statement in the string line (e.g. OUTPUT(LBL)), e.g., OUTPUT(g1). Create and insert a new circuit_item with the name LBL of type Wire and add this to the hash table. Subsequently, store a pointer to it on index wr_index of the array outputs[]. Use for the hash key the name LBL as well. Finally, increment the value of wr_index to prepare for the next OUTPUT statement. Hint: Use the function implemented in Task G to create and add wires to the hash table. Finally, implement the function that handles <GATE> statements. void gate_statement (char *line, node *table[], int table_size) This function contains a <GATE> statement in the string line (e.g. LBLx = AND(LBL1, LBL2, LB3, )), e.g., g2 = AND(g3, g4, g5). Create a new circuit_item with the name ANDLBLx (i.e., the gate type concatenated with the output wire name of the gate) of type Gate and add this to hash table. Use for the hash key the same gate name ANDLBLx. The input wires of the gate LBL1, LBL2, have to be created and added to the hash table if they do not exist already.You can use the function circuit_item *get_or_insert_wire_item(node *table[], int table_size, char *str) for this. Connect the inputs of the gate to wires LBL1, LBL2, (i.e., create a mutual connection in both directions). For example, in Figure 3.1, wire g126 is connected to gate ANDg140. The following code illustrates this mutual connection: circuit_item *wire_g126; circuit_item *gate_g140; GET_WIRE(wire_g126).out[0] = gate_g140; GET_GATE(g126).in[0] = wire_g126; The function-like defined macros GET_WIRE() and GET_GATE() are defined below in the hints section. Similarly, connect the output of the gate to the wire LBLx (i.e., create a mutual connection in both directions). Hints: A gate has variable input sizes. Determine first the number of inputs before you allocate memory for them. You can determine the number of inputs by creating the function int count_occurences(char line[], char id). Given the string line, it will count the number of occurrences of character id in the string. You can count the number of commas. Use the function implemented at Task F to assign the gate types. Each input of the gate is connected to a wire. At the beginning of reading in the netlist, the total number of output branches that a wire contains is not know. Therefore, each time we encounter such a wire, we can increment its number of output connections. This requires, however, a reallocation for the vector with output branches (i.e., the array out of the structure s_wire). Use the builtin function realloc() for this. Use the function circuit_item *get_or_insert_wire_item(node *table[], int table_size, char *str) implemented at Task G to check for the existence of wires. You may use the function strtok() to obtain certain tokens from a string. See Appendix B3 of the C book. Use the function-like defined macros in netlist.h. #define GET_GATE(x) (x->cir_type.circuit_gate) #define GET_WIRE(x) (x->cir_type.circuit_wire) These macros increase code readability and reduce programming errors. You can only use these macros if you are sure that the circuit items are of the correct type. Consider the following small fragment of code demonstrating how to use them.

13

circuit_item *ptr; ptr->wire.type = Wire; int no_outputs = GET_WIRE(ptr).no_outputs; /* get the number of output branches of the wire. The line is equivalent to: int no_outputs = (ptr-> cir_type.circuit_wire).no_outputs */ I. In this task, you have to group the functionality of the previous functions and create a wrapper around them. Implement the function: void read_netlist(FILE *fp, s_interface *interface, node *table[], int table_size) This function reads the file fp completely and interprets all statements. First, allocate proper memory for fields of the argument interface (i.e., interface->inputs, interface->outputs). Subsequently, read the file fp completely and assign all circuit items to the proper arrays. Hints: Use the function at Task E to determine how much memory must be allocated for the fields of the argument interface. Use a loop to process the file line by line. Use the function at Task C to read a line from the file Use the functions at Task D to determine the type of statement Use the functions at Task G to interpret each statement. J. Our primary objective is reading a netlist is to perform simulations (Assignment 3). In this task, however, we will verify whether the netlist has been read correctly. We achieve this by generating a new output netlist using the defined data structures. Implement the function: void write_netlist(FILE *fp_out, s_interface *interface, node *table[], int table_size) Write to file fp_out a netlist of the circuit based on the content of the other arguments. The first step is to write all INPUT and OUPUT statements using the argument interface. Subsequently, scan through all elements of the hash table (using the argument table) and look for GATE statements only. Be aware of the fact that multiple items can be stored on the same hash index in the form of a list. Hint: The function struct circuit_item *get_circuit_item(node *table[], int size, char *key ) contains an example of how to iterate over all the circuit_items on a specific index of the hash table. In order to obtain the same output netlist as input netlist, a hash size of 1 must be used. K. The memory that has been allocated for this assignment must be freed properly. Implement the function: void clean_hashtable(node *table[], int size) This function de-allocates all memory pointed by the non-zero entries of the table. Thereafter the hash table itself should be de-allocated. You can test this function by inserting manual entries to the hash table. Implement this function in the file hash.c. Hint: Make sure that the in and out members of the structs s_wire and s_gate are freed properly. L. Finally, combine all the previous functionalities in the function main() The main includes the following tasks: Create a hash table of size=101 using the function node** create_hash_table(int size) Read a netlist from a file (you can use any of the benchmark files) Generate the net list Clean the memory. Make sure all memory is freed correctly. Make sure that the provided benchmarks (assingment1.bench, c432.bench, c6288.bench) produce correct netlists. Again, for test purposes use a hash table size of 1. This will generate an output netlist that equals the provided input netlist.

14

4 Assignment 3: Netlist Simulation


4.1 Required knowledge In order to complete this assignment, understanding of the following topics is required: Knowledge of Assignments 1 and 2 Linked lists data structures Timing in C Command line arguments 4.2 Objectives

After this assignment students should be able to: Perform similar operations as in Assignment 2 Use linked lists Compute the execution time Learn to modify programs Get familiar with reading program arguments from the command line Use the math library 4.3 Problem statement

In Assignment 1 the two simulation modes User defined and Exhaustive simulation were developed and simulated. In the previous assignment a Verilog netlist was read from a file. The purpose of this assignment is to simulate the netlist using a third simulation mode referred to as Testbench simulation. In this simulation mode, input stimuli are read from a file. The application is run and controlled using program arguments specified from the command line. These arguments include: (a) the filename of the netlist, (b) the simulation mode, (c) the input testbench file and (d) the output response file. In summary, the following arguments along with the executable name can be specified for the 3 different simulations modes:

User defined: <executable name> -n <netlist_name> t user_defined Exhaustive: <executable name> -n < netlist_name> t exhaustive
Testbench: <executable name> -n < netlist_name> t testbench i <testbench_file> [o <output_response_file>]

Where:

executable name: denotes the executable file (such as a.out in Linux or simulation.exe in Windows) -n : this option specifies the input file of the simulator consisting of the netlist -t : this option specifies the simulation mode (such as User defined and Testbench simulation) -i : this option specifies the input file name containing the testbench. This is only applicable to the Testbench simulation. -o: this option specifies the output response file. Note that this argument is optional. In case not provided, a default filename sim_result.txt is used

The correctness of the code is verified by performing simulations using the example circuit of Figure 2.1 of Assignment 1. The netlist of this circuit, assignment1.bench, was also used in Assignment 2. The User and Exhaustive simulations are similar as to the ones described in Assignment 1, with the exception that the simulation is performed on a netlist instead of the hardcoded circuit of Figure 2.1. The testbench file contains a list of input stimuli; each input vector contains an additional timestamp denoting at what time it is applied to the circuit. The format of the testbench files is defined in Figure 4.1; the left-hand side contains the general file format, while the right-hand side shows a simple example.
% line containing comment <input vector> <input vector> <timestamp> <timestamp> % test bench with 3 input vectors % applied at time 10.5, 20.0 and 40.1 0000 10.5 010X 20.0 X100 40.1

Figure 4.1 - Testbench format description

15

The output file for the Testbench simulation mode consists of the output values and the associated timing information at which they were changed/updated. Figure 4.2 shows an example of such a file; e.g., the output 10 is realized at time 14.3.
%output vector - time unit 10 14.3 11 21.7 X1 22.2 01 44.1

Figure 4.2 - Example output of a Testbench simulation All simulations are based on events (transitions) that occur in the circuit. Each event updates a new wire value and subsequently evaluates the next gate connected to the wire. The events are stored in a linked list and executed one at a time. The initial set of events are based on the input vectors from the screen (User defined simulation), generated automatically (Exhaustive simulation) or from the testbench file (Testbench simulation). Subsequently, new events are created and scheduled based on the propagation of the signals through the circuit gates. The simulation terminates when all inputs stimuli are applied and when all the events completed. New events are added on the current event list based on transitions that take place in the circuits. We use the following timing information to generate new events, i.e., the delays used for the gates and wires: Assume a delay of 5 units for an inverter gate and 15 units for the remainder 2 input gates. The delay of an n-input gate is assumed to be log2(n)*delay of a 2-input gate (i.e, log2(n)*15) Assume a delay of 0.05*no_outputs units for wires, where no_outputs represents the number of branches a wire contains that are connected to gates (as defined in the structure s_wire in netlist.h) Input and output wires have an additional delay of 0.1 units Figure 4.3 illustrates the delay calculation based on the above assumptions. Table 4.1 explains the composition of the wire delays.

Figure 4.3 Delay calculation for gates and wires Wire a1 a2 a3 g3 g4 g1 g2 Input delay Output delay no_outputs*0.05 0.1 0.1 0 0.1 0 0.05 0.1 0 0.05 0 0.1 0.05 0 0.2 0 0 0 0.05 0 0 0.15 Table 4.1 Delay composition of wires Sum delay 0.2 0.15 0.15 0.15 0.2 0.05 0.15

In the assignment, it is not allowed to use global variables; it is also not allowed to change the provided declarations. Use meaningful variables, constants, etc. and use enough comments to make your code easy to read and understand. Do not forget to write the report.

16

4.4

The assignment A. In this task, we prepare the files required for this assignment. We build on the files of Assignment 2. Create 2 new files simulation.c and simulation.h. All the functions in this assignment must be implemented in these files, unless specified otherwise. Move the main function from netlist.c and place it in simulations.c. Include the proper header files in simulation.c and test if you get the same output as obtained in Assignment 2. If you are using the provided Makefile of Assignment 2 you have to modify this to include the additional source file. Once you manage to compile and run the files, the next step is to remove the line that calls the function void write_netlist(FILE *fp_out, s_interface *interface, node *table[], int table_size) from the main() function as this is not used in this assignment. Copy the content of Task A (definitions of identifiers) and B (implementation of gates) of Assignment 1 into the simulation.c and simulation.h files. Place these contents in the proper .c or .h files. Create an enumeration enum combinational_type {User, Exhaustive, Testbench}; this enumeration specifies the simulation mode. B. As you probably have noticed from the netlists, some gates have more than two inputs. Therefore, we have to extend the truth tables introduced in Figure 2.2 in order to comply with multi-input gates. Implement the functions: 1. 2. 3. 4. 5. 6. int vector_or_gate (int inputs[], int size) int vector_nor_gate (int inputs [], int size) int vector_and_gate(int inputs [], int size) int vector_nand_gate (int inputs [], int size) int vector_xor_gate (int inputs [], int size) int vector_xnor_gate (int inputs [], int size)

These are generalized functions of those of Assignment 1 Task B. The functions evaluate gates with size number of inputs, where inputs[i] contains the i-th input of the gate. Again, only the values LOGIC_1, LOGIC_2 and LOGIC_X can be expected for the entries of the array. Hint: Use the functions implemented in Assignment 1 Task B. C. All inputs and outputs of gates and wires can have certain logic values. However, on this assignment we assume that only wires hold values. Add the member value of type int to the structure s_wire in netlist.h. This member stores the logic value of the wire (e.g., LOGIC_0, LOGIC_1, LOGIC_X) during the simulation. To support wire and gate delays, a new member delay of type double must be added to the s_gate and s_wire structures in netlist.h. Modify the functions of Assignment 2 Task H to implement this functionality. Initialize the values of each wire to LOGIC_X and calculate the delay of the wires and gates properly as specified in the problem statement. Hints: Initialize the gate delay in the function void gate_statement (char *line, node *table[], int table_size) of Assignment 2 Task H after you have determined its type and number of inputs. Initialize the wire value to LOGIC_X and delay to 0.0 in the function circuit_item *get_or_insert_wire_item(node *table[], int table_size, char *str) of Assignment 2 Task G in case the wire did not existed in the hash table, thus do this only for the new created wires. The extra wire delay of 0.1 for inputs and outputs of the circuit must be updated in the void input_statement (char *line, circuit_item *inputs[], int *wr_index, node *table[], int table_size ) and void output_statement(char *line, circuit_item *outputs[], int *wr_index, node *table[], int
table_size ) functions respectively.

17

Update the wire delay each time the number of output branches of a wire is increased. You have to carry this out in the void gate_statement(char *line, node *table[], int table_size ) function for each input wire of the gate. D. The delay of each wire and gate must be printed on the screen. Implement the function: double print_timing_information(node *table[], int table_size) This function scans through all the items stored in the hash table and prints for each wire and each gate its delay. Perform the printing in the following manner: <type> wire ... Gate <name> g13 andg13 <delay> 0.10 15

Print first all wire delays followed by the gate delays. You can check your code with the following netlist: input(a1) input(a2) input(a3) output(a1) output(g3) output(g4) output(g4) g1 g2 g3 g4 = = = = not(a3) or(g1,a2) nand(g2,g2,g2) not(g3)

This netlist describes the gates and wires depicted in Figure 4.3. Make sure that all the delays are equal to the ones specified in the figure. This function returns a double that contains the execution time in ms of this function, i.e., the total time required to execute all the code of this function. Hint: In order to print the total execution time you have to include the time.h header file. See appendix B10 of the C-course book that specifies how to use timers. E. The simulations are performed by keeping a linked list of events. In this task, we define the structure for the linked list. Add the following structure that defines the content of an event to your code. typedef struct event { double time_stamp; circuit_item *item; int update_value; struct event *next_event; } event; The structure contains 4 fields. The first field contains the time stamp of the event of the particular wire. The second field contains a pointer to that particular wire that needs to be updated. The third argument contains the updated value, i.e., the new value (update_valule) for the wire item. Finally, a pointer to the next event is stored. Again, we assume that only wires hold values in the simulations, i.e. only wire events occur. F. Events are generated by switching activities in the circuit. Implement the function: void schedule_event(event **event_list, double time_stamp, circuit_item *item, int update_value)

18

This function schedules a new event using the last 3 function arguments and inserts it to the list of events in event_list. The event should be inserted in the list based on the argument time_stamp. The time_stamp denotes at which time the event takes place. Schedule this new event as late as possible, in order to reduce the total number of events that take place during simulation. Figure 4.4 shows an example with a new event scheduled at time 20. Since there are already 2 events with this time stamp, the new event should be inserted after the other 2.

Current event list:


Time stamp: 10 Time stamp: 20 Time stamp: 20 Time stamp: 30

New event:
Time stamp: 20

Insert this new event here

Figure 4.4 - Scheduling events The argument **event_list contains the address of the pointer that points to the first element of the list. The reason to use this construction is that in some cases the first element of the event can be changed. Its address is used in order to make this change visible outside the function. For example, in case a new event with time stamp 8 is added to the event list of Figure 4.4 it must be included at the start of the event list. The following code illustrates the usage of how this function is called: event *list; // some other operations schedule_event(&list, ); // address of list is passed as argument Hint: Create first a new event (with the proper memory allocation) and subsequently traverse over the linked list to find the proper place to insert it. Be careful with NULL pointers. G. The first set of events are created from the input vectors. Implement the function: void add_input_events(int inputs[], int bitmask[], double time_stamp, s_interface *interface, event **event_list) The vector inputs contains the values (e.g. LOGIC_0) of the circuit inputs defined at time time_stamp that need to be scheduled in the event list. The array bitmask specifies which elements of the inputs need to be scheduled. If bitmask[i] equals zero then inputs[i] should not be scheduled. In case it is 1, it should. The time you have to schedule the event is determined by the delay of the wire. For example, in case the input wire delay is 2 units, you have to schedule this wire at the current time_stamp + 2. Assume that the values of the array inputs are placed in the same order as the wires specified in interface->inputs. The values in the array inputs are either: LOGIC_0, LOGIC_1 or LOGIC_X. Hint: Use the function void schedule_event(event **event_list, double time_stamp, circuit_item *item, int update_value) of Task E and calculate the argument time_stamp properly. You can use the function dump_schedule(event *event_list) provided below to test the content of the list of events. You may modify the print statement in this function as well and add your own debug/test functions. However, remove any auxiliary test functions from the final code including dump_schedule(). void dump_schedule(event *event_list) { event *curr = event_list; while(curr) {

19

printf("%s at time stamp %f\n", curr->item->name, curr->time_stamp); curr = curr->next_event; } } H. The other events (gates and non-input wires), are evaluated only during the simulation. Implement the function: void sim_single_event(event **event_list) This function simulates the first element of the event list by updating the value of the wire specified in the event; both the value and the wire are members of the structure event. Next, evaluate the output of the subsequent gate and schedule a new event for the wire connected to the output of this gate. The new time stamp for the output wire of this gate event depends on the current time stamp and the gate and wire delay. In case the wire of the current event has multiple output branches, schedule all the output wires of the gates this wire is connected to. In case the wire has no output (no_outputs = 0), i.e., it is solely an output wire of the circuit, nothing has to be done. Hints: For each gate that is connected to the output branches of the wire, its truth table must be evaluated by using the functions implemented in Task B. Use the function void schedule_event(event **event_list, double time_stamp, circuit_item *item, int update_value) to schedule the new events. Use for update_value the result obtained from gate evaluation. Consider as an example the wire g3 in Figure 4.3. Assume that this is the first event of the event list and that its value must be updated to LOGIC_0 at time_stamp 20.0. First, you have to update the value to LOGIC_0 (the member value of struct s_wire of wire g3). Subsequently, you have to schedule wire g4 at time 25.2. This timestamp is obtained as follows: 20.0 (current timestamp) + 5.0 (delay of not gate) + 0.2 (wire delay of g4). The new event scheduled for wire g4 must have an update_value of LOGIC_1, as that is the new output of the not gate. After the first event is simulated, the event_list must be updated to point to the next element in the list. Make sure you free the memory of the simulated event properly. I. For both User defined and Exhaustive simulation, we are interested only in the output responses of the circuit. The output responses are obtained by simulating all the events that are present in the event list. Simulate all events starting from the first element in the list of events by implementing the following function: void sim_all_events(event **event_list) Hint: Use the function void sim_single_event(event **event_list) J. In this task, we implement the User defined and Exhaustive simulation modes. Implement the function: double user_sim(s_interface *interface) that has the same functionality as described in Assignment 1 Task E. Ask the user to specify an input vector. In addition, provide the user with a statement of what the size of this input should be. Print the resulting output vector on the screen. Reuse the function void print_vector(int vect[], int size) of Assignment 1 Task E. This function returns a double that contains the execution time in ms of this function, i.e., the total time required to execute all the code of this function.

20

Hints: Use the function void add_input_events(int inputs[], int bitmask[], double time_stamp, s_interface *interface, event **event_list) to add the specified input vector to the event list. Subsequently, use the function void sim_all_events(event **event_list) to simulate the circuit and determine the outputs of the circuit. Read the outputs of the circuit by using the interface structure and convert them into an array. Subsequently use void print_vector(int vect[], int size) to print this output. Similarly, implement the function: double exhaustive_sim(s_interface *interface) (see Task 1.F for its functionality). Hints: Use the function void sim_all_events(event **event_list) for each input vector. Optimize the number of events by using the bitmask array wisely. K. In the current and next task, we focus on simulations for the Testbench simulation mode. In this task, you have to read the testbench file (see Figure 4.1 for its format) and schedule new events for all input stimuli. In the next task the input stimuli are actually simulated. Implement the function: void init_TB_based_events(FILE *fp_tb, s_interface *interface, event ** event_list) This function reads the file fp_tb that contains the test bench. Schedule the input stimuli using the function void add_input_events(int inputs[], int bitmask[], double time_stamp, s_interface *interface, event **event_list). However, you should only schedule those inputs that change only. Consider for example the following two vectors: 110X 30.0 1101 50.0 For the second vector only the first input has to be scheduled as it is the only changing input. Hint: Use the function void schedule_event(event **event_list, double time_stamp, circuit_item *item, int update_value) to schedule the new events L. Next, simulate all the input stimuli that are added at Task J. Implement the function: double testbench_sim(FILE *fp_tb, s_interface *interface, event **event_list, FILE *fp_out) First call the function void init_TB_based_events(FILE *fp_tb, s_interface *interface, event ** event_list) to initialize all the events. Subsequently, simulate all the events specified in the event_list and write the output responses to the file fp_out. Each time an output changes write the complete output vector to the file fp_out and the time stamp of this occurrence. Optimize and reduce the number of output print statements. The following output is considered incorrect: % outputs X101 0101 0101 0101 0111 % time 20.0 23.0 25.0 27.0 28.0

% output didn't change % output didn't change

Instead, it should be printed like this: % outputs X101 0101 0111 % time 20.0 23.0 28.0

21

This function returns a double that contains the execution time in ms of this function, i.e., the total time required to execute all the code of this function. Hint: use the function void sim_single_event(event **event_list). After each time a single event is evaluated check if the values of the output of the circuit have changed. M. As there are 3 simulation modes, faulty command line inputs can be easily specified. In case a faulty option is specified, the correct usage of the simulation modes should be printed briefly. Implement the function: void print_usage() This function will print all possible command line arguments that the simulator can read in. N. Modify the function int main(int argc, char **argv) to include the new functionality. Read the arguments of the program using the argc and argv arguments. In case of faulty input the correct usage should be printed (Task M). Perform the simulations based on the simulation mode specified in the program arguments (see Section 4.3). After the simulation is performed, print on the screen the timing information of the gates using the function double print_timing_information(node *table, int size); Hints: Use enum combinational_type to distinguish between the simulation modes while determining the simulation type. You may use getopt() to read the command line arguments. See for example: http://www.gnu.org/s/hello/manual/libc/Example-of-Getopt.html#Example-of-Getopt Make sure you have cleaned all memory allocated during the assignment. O. When the application has completed, print the following timing information: - Time required to perform the simulation: < specify your time here in milliseconds > - Time required to print delay the information: < specify your time here in milliseconds > - Total application time: < specify your time here in milliseconds > An example output is the following: Time required to perform the simulation: 2.0 ms. Time required to print the delay information: 0.1 ms. Total application time: 4.0 ms. The timing is measured for the following functions: - either the function user_sim(), exhaustive_sim() or testbench_sim() based on the simulation mode - the function print_timing_information() - the function main() Hints: Use the return value of the functions user_sim(), exhaustive_sim(), testbench_sim() and print_timing_information() to obtain their execution time. Add a new timer to measure the whole application time. P. This task is optional In this task we are going to modify the print statements in the function double print_timing_information(node *table[], int table_size). So far, all wires and gates were printed more or less in random order in this function, based on their position in the hash table. In this task, you have to modify the printing in such a way that all wires and gates in alphabetic order. For example, the netlist in assignment1.bench should generate the following output: wire wire wire wire wire wire a0 a1 a2 a3 b0 b1

22

wire wire wire wire wire wire Gate Gate Gate Gate Gate Gate Gate Gate

g0 g1 g2 g3 g4 g5 andg4 nandb1 norg2 notg0 notg1 org5 xnorb0 xorg3

On the dots you have to specify your own delays. Hints: Modern C compilers have a built-in quicksort function declared as follows: void qsort(void *base, size_t nmemb, size_t width, int (*compare)(const void *, const void *)); Here: base is a pointer to the beginning of data array; the data array can be of any type. In our case the array is considered to be consisting of circuit_item pointers. nmemb contains the number of elements in the array. width is the size of each element (in bytes). compare is a callback function; this performs the actual comparison function to implement the sorting. For example, if we want to sort input wires, we proceed as follows: qsort((void *) interface->inputs, (size_t) interface->no_inputs, sizeof(circuit_item*), compare); The sorted array is stored in the original specified array. The sorting is based on the compare function. You have to implement your own compare function in the following way: int compare( const void *arg1, const void *arg2 ) In this compare function you have typecast arg1, and arg2 to obtain pointers of type circuit_items. Subsequently, you can use the function strcmp() to compare the names of the circuit_items. Q. Test your code by simulation the circuit of assignment 1 (assignment1.bench). You can download this file from blackboard. Write your own testbench file and specify therein your input vectors with their timestamps. This testbench file must be submitted in a separate file as well. Congratulations, you have made your own testbench simulator.

23

You might also like