This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
geda:icarus_vvp_simulation [2007/05/24 22:27] 127.0.0.1 external edit |
geda:icarus_vvp_simulation [2012/02/20 15:14] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== VVP Simulation Engine ====== | ||
+ | <code>/* | ||
+ | * Copyright (c) 2001 Stephen Williams (steve@icarus.com) | ||
+ | * | ||
+ | * $Id: README.txt,v 1.74 2005/11/25 17:55:26 steve Exp $ | ||
+ | */ | ||
+ | VVP SIMULATION ENGINE | ||
+ | |||
+ | The VVP simulator takes as input source code not unlike assembly | ||
+ | language for a conventional processor. It is intended to be machine | ||
+ | generated code emitted by other tools, including the Icarus Verilog | ||
+ | compiler, so the syntax, though readable, is not necessarily | ||
+ | convenient for humans. | ||
+ | |||
+ | |||
+ | GENERAL FORMAT | ||
+ | |||
+ | The source file is a collection of statements. Each statement may have | ||
+ | a label, an opcode, and operands that depend on the opcode. For some | ||
+ | opcodes, the label is optional (or meaningless) and for others it is | ||
+ | required. | ||
+ | |||
+ | Every statement is terminated by a semicolon. The semicolon is also | ||
+ | the start of a comment line, so you can put comment text after the | ||
+ | semicolon that terminates a statement. Like so: | ||
+ | |||
+ | Label .functor and, 0x5a, x, y ; This is a comment. | ||
+ | |||
+ | The semicolon is required, whether the comment is there or not. | ||
+ | |||
+ | Statements may span multiple lines, as long as there is no text (other | ||
+ | then the first character of a label) in the first column of the | ||
+ | continuation line. | ||
+ | |||
+ | HEADER SYNTAX | ||
+ | |||
+ | Before any other non-commentary code starts, the source may contain | ||
+ | some header statements. These are used for passing parameters or | ||
+ | global details from the compiler to the vvp run-time. In all cases, | ||
+ | the header statement starts with a left-justified keyword. | ||
+ | |||
+ | * :module "name" ; | ||
+ | |||
+ | This header statement names a vpi module that vvp should load before | ||
+ | the rest of the program is compiled. The compiler looks in the | ||
+ | standard VPI_MODULE_PATH for files named "name.vpi", and tries to | ||
+ | dynamic load them. | ||
+ | |||
+ | * :vpi_time_precision [+|-]<value>; | ||
+ | |||
+ | This header statement specifies the time precision of a single tick of | ||
+ | the simulation clock. This is mostly used for display (and VPI) | ||
+ | purposes, because the engine itself does not care about units. The | ||
+ | compiler scales time values ahead of time. | ||
+ | |||
+ | The value is the size of a simulation tick in seconds, and is | ||
+ | expressed as a power of 10. For example, +0 is 1 second, and -9 is 1 | ||
+ | nanosecond. If the record is left out, then the precision is taken to | ||
+ | be +0. | ||
+ | |||
+ | LABELS AND SYMBOLS | ||
+ | |||
+ | Labels and symbols consist of the characters: | ||
+ | |||
+ | a-z | ||
+ | A-Z | ||
+ | 0-9 | ||
+ | .$_<> | ||
+ | |||
+ | Labels and symbols may not start with a digit or a '.', so that they | ||
+ | are easily distinguished from keywords and numbers. A Label is a | ||
+ | symbol that starts a statement. If a label is present in a statement, | ||
+ | it must start in the first text column. This is how the lexical | ||
+ | analyzer distinguishes a label from a symbol. If a symbol is present | ||
+ | in a statement, it is in the operand. Opcodes of statements must be a | ||
+ | keyword. | ||
+ | |||
+ | Symbols are references to labels. It is not necessary for a label to | ||
+ | be declared before its use in a symbol, but it must be declared | ||
+ | eventually. When symbols refer to functors, the symbol represents the | ||
+ | vvp_ipoint_t pointer to the output. (Inputs cannot, and need not, be | ||
+ | references symbolically.) | ||
+ | |||
+ | If the functor is part of a vector, then the symbol is the | ||
+ | vvp_ipoint_t for the first functor. The [] operator can then be used | ||
+ | to reference a functor other then the first in the vector. | ||
+ | |||
+ | There are some special symbols that in certain contexts have special | ||
+ | meanings. As inputs to functors, the symbols "C<0>", "C<1>", "C<x>" | ||
+ | and "C<z>" represent a constant driver of the given value. | ||
+ | |||
+ | |||
+ | SCOPE STATEMENTS: | ||
+ | |||
+ | |||
+ | PARAMETER STATEMENTS: | ||
+ | |||
+ | Parameters are named constants within a scope. These parameters have a | ||
+ | type and value, and also a label so that they can be referenced as VPI | ||
+ | objects. | ||
+ | |||
+ | The syntax of a parameter is: | ||
+ | |||
+ | <label> .param <name>, <type>, <value>; | ||
+ | |||
+ | The <name> is a string that names the parameter. The name is placed in | ||
+ | the current scope as a vpiParameter object. The <type> is one of the | ||
+ | following: | ||
+ | |||
+ | real -- The parameter has a real value | ||
+ | string -- The parameter has a string value | ||
+ | [<msb>,<lsb>,<s>] | ||
+ | -- The parameter is a vector, with specified | ||
+ | indices. The <s> is s or u for signed or | ||
+ | unsigned. | ||
+ | |||
+ | The value, then, is appropriate for the data type. For example: | ||
+ | |||
+ | P_123 .param "hello", string, "Hello, World."; | ||
+ | |||
+ | |||
+ | FUNCTOR STATEMENTS: | ||
+ | |||
+ | A functor statement is a statement that uses the ``.functor'' | ||
+ | opcode. Functors are the basic structural units of a simulation, and | ||
+ | include a type (in the form of a truth table) and up to four inputs. A | ||
+ | label is required for functors. | ||
+ | |||
+ | The general syntax of a functor is: | ||
+ | |||
+ | <label> .functor <type> [ (<delay>) ], symbol_list ; | ||
+ | |||
+ | The symbol list is 4 names of labels of other functors. These connect | ||
+ | inputs of the functor of the statement to the output of other | ||
+ | functors. If the input is unconnected, use a C<?> symbol instead. The | ||
+ | type selects the truth lookup table to use for the functor | ||
+ | implementation. Most of the core gate types have built in tables. | ||
+ | |||
+ | The initial values of all the inputs and the output is x. Any other | ||
+ | value is passed around as run-time behavior. If the inputs have C<?> | ||
+ | symbols, then the inputs are initialized to the specified bit value, | ||
+ | and if this causes the output to be something other then x, a | ||
+ | propagation event is created to be executed at the start of run time. | ||
+ | |||
+ | The strengths of inputs are ignored by functors, and the output has | ||
+ | fixed drive0 and drive1 strengths. So strength information is | ||
+ | typically lost as it passes through functors. | ||
+ | |||
+ | Almost all of the structural aspects of a simulation can be | ||
+ | represented by functors, which perform the very basic task of | ||
+ | combining up to four inputs down to one output. | ||
+ | |||
+ | - MUXZ | ||
+ | |||
+ | Q | A B S n/a | ||
+ | --+------------- | ||
+ | A | * * 0 | ||
+ | B | * * 1 | ||
+ | |||
+ | |||
+ | DFF STATEMENTS: | ||
+ | |||
+ | The Verilog language itself does not have a DFF primitive, but post | ||
+ | synthesis readily creates DFF devices that are best simulated with a | ||
+ | common device. Thus, there is the DFF statement to create DFF devices: | ||
+ | |||
+ | <label> .dff <d>, <clk>, <ce>, <async-input>; | ||
+ | |||
+ | The generated functor is generally synchronous on the <clk> rising | ||
+ | edge of <clk>, with the <ce> enable active high. The <clk> and <ce> | ||
+ | are single bit vectors (or scalars) on ports 1 and 2. Port-0 is any | ||
+ | type of datum at all. The device will transfer the input to the output | ||
+ | when it is loaded by a clock. The <async-input> is a special | ||
+ | asynchronous input that is immediately stored and transferred to the | ||
+ | output when data arrives here. This is useful for implementing | ||
+ | asynchronous set/clear functions. | ||
+ | |||
+ | UDP STATEMENTS: | ||
+ | |||
+ | A UDP statement either defines a User Defined Primitive, or | ||
+ | instantiates a previously defined UDP by creating a UDP functor. A | ||
+ | UDP functor has as many inputs as the UDP definition requires. | ||
+ | |||
+ | UDPs come in sequential and combinatorial flavors. Sequential UDPs | ||
+ | carry an output state and can respond to edges at the inputs. The | ||
+ | output of combinatorial UDPs is a function of its current inputs | ||
+ | only. | ||
+ | |||
+ | The function of a UDP is defined via a table. The rows of the table | ||
+ | are strings which describe input states or edges, and the new output | ||
+ | state. Combinatorial UDPs require one character for each input, and | ||
+ | one character at the end for the output state. Sequential UDPs need | ||
+ | an additional char for the current state, which is the first char of | ||
+ | the row. | ||
+ | |||
+ | Any input transition or the new state must match at most one row (or | ||
+ | all matches must provide the same output state). If no row matches, | ||
+ | the output becomes 1'bx. | ||
+ | |||
+ | The output state can be specified as "0", "1", or "x". Sequential | ||
+ | UDPs may also have "-": no change. | ||
+ | |||
+ | An input or current output state can be | ||
+ | |||
+ | "1": 1 | ||
+ | "0": 0 | ||
+ | "x": x | ||
+ | "b": 1, 0 | ||
+ | "h": 1, x | ||
+ | "l": 0, x | ||
+ | "?": 1, 0, x | ||
+ | |||
+ | For Sequential UDPs, at most one input state specification may be | ||
+ | replaced by an edge specification. Valid edges are: | ||
+ | |||
+ | "*": (??) "_": (?0) "+": (?1) "%": (?x) | ||
+ | "P": (0?) "r": (01) "Q": (0x) | ||
+ | "N": (1?) "f": (10) "M": (1x) | ||
+ | "B": (x?) "F": (x0) "R": (x1) | ||
+ | |||
+ | "n": (1?) | (?0) | ||
+ | "p": (0?) | (?1) | ||
+ | |||
+ | A combinatorial UDP is defined like this: | ||
+ | |||
+ | <type> .udp/comb "<name>", <number>, "<row0>", "<row1>", ... ; | ||
+ | |||
+ | <type> is a label that identifies the UDP. <number> is the number of | ||
+ | inputs. "<name>" is there for public identification. Sequential UDPs | ||
+ | need an additional initialization value: | ||
+ | |||
+ | <type> .udp/sequ "<name>", <number>, <init>, "<row0>", "<row1>", ... ; | ||
+ | |||
+ | <init> is the initial value for all instances of the UDP. We do not | ||
+ | provide initial values for individual instances. <init> must be a | ||
+ | number 0, 1, or 2 (for 1'bx). | ||
+ | |||
+ | A UDP functor instance is created so: | ||
+ | |||
+ | <label> .udp <type>, <symbol_list> ; | ||
+ | |||
+ | Where <label> identifies the functor, <type> is the label of a UDP | ||
+ | defined earlier, and <symbol_list> is a list of symbols, one for each | ||
+ | input of the UDP. | ||
+ | |||
+ | |||
+ | VARIABLE STATEMENTS: | ||
+ | |||
+ | A variable is a bit vector that can be written by behavioral code (so | ||
+ | has no structural input) and propagates its output to a functor. The | ||
+ | general syntax of a variable is: | ||
+ | |||
+ | <label> .var "name", <msb>, <lsb>; | ||
+ | <label> .var/s "name", <msb>, <lsb>; | ||
+ | <label> .var/real "name", <msb>, <lsb>; | ||
+ | |||
+ | The "name" is the declared base name of the original variable, for the | ||
+ | sake of VPI code that might access it. The variable is placed in the | ||
+ | current scope. The variable also has a width, defined by the indices | ||
+ | for the most significant and lest significant bits. If the indices are | ||
+ | equal (normally 0) the vector has width of one. If the width is greater | ||
+ | then one, a contiguous array of functors is created and the value of | ||
+ | the label is the address of the least significant bit. | ||
+ | |||
+ | A variable does not take inputs, since its value is set behaviorally | ||
+ | by assignment events. It does have output, though, and its output is | ||
+ | propagated into the net of functors in the usual way. | ||
+ | |||
+ | A variable gets its value by assignments from procedural code: %set | ||
+ | and %assign. These instructions write values to the port-0 input. From | ||
+ | there, the value is held. | ||
+ | |||
+ | Behavioral code can also invoke %cassign/v statements that work like | ||
+ | %set/v, but instead write to port-1 of the variable node. Writes to | ||
+ | port-1 of a variable activate continuous assign mode, where the values | ||
+ | written to port-0 are ignored. The continuous assign mode remains | ||
+ | active until a long(1) is written to port-3 (a command port). | ||
+ | |||
+ | Behavioral code may also invoke %force/v statements that write to port-2 | ||
+ | to invoke force mode. This overrides continuous assign mode until a | ||
+ | long(2) is written to port-3 to disable force mode. | ||
+ | |||
+ | NET STATEMENTS: | ||
+ | |||
+ | A net is similar to a variable, except that a thread cannot write to | ||
+ | it (unless it uses a force) and it is given a different VPI type | ||
+ | code. The syntax of a .net statement is also similar to but not | ||
+ | exactly the same as the .var statement: | ||
+ | |||
+ | <label> .net "name", <msb>, <lsb>, <symbol>; | ||
+ | <label> .net/s "name", <msb>, <lsb>, <symbol>; | ||
+ | <label> .net8 "name", <msb>, <lsb>, <symbol>; | ||
+ | <label> .net8/s "name", <msb>, <lsb>, <symbol>; | ||
+ | <label> .net/real "name", <msb>, <lsb>, <symbol>; | ||
+ | <label> .alias "name", <msb>, <lsb>, <symbol>; | ||
+ | |||
+ | |||
+ | Like a .var statement, the .net statement creates a VPI object with | ||
+ | the basename and dimensions given as parameters. The symbol is a | ||
+ | functor that feeds into the vector of the net, and the vpiHandle | ||
+ | holds references to that functor. | ||
+ | |||
+ | The input of a .net is replicated to its output. In this sense, it | ||
+ | acts like a diode. The purpose of this node is to hold various VPI | ||
+ | and event trappings. The .net and .net8 nodes are vector types. They | ||
+ | both may represent wires, but the .net8 nodes preserve strength values | ||
+ | that arrive through them, while .net nodes reduce strength values to | ||
+ | 4-value logic. The .net8 nodes should only be used when strength | ||
+ | information really is possible. | ||
+ | |||
+ | The <label> is required and is used to locate the net object that is | ||
+ | represents. This label does not map to a functor, so only references | ||
+ | that know they want to access .nets are able to locate the symbol. In | ||
+ | particular, this includes behavioral %load and %wait instructions. The | ||
+ | references to net and reg objects are done through the .net label | ||
+ | instead of a general functor symbol. The instruction stores the | ||
+ | functor pointer, though. | ||
+ | |||
+ | The .alias statements do not create new nodes, but instead create net | ||
+ | names that are aliases of an existing node. This handles special cases | ||
+ | where a net has different names, possibly in different scopes. | ||
+ | |||
+ | MEMORY STATEMENTS: | ||
+ | |||
+ | Memories are arrays of words, each word a vvp_vector4_t vector of the | ||
+ | same width. The memory is canonically addressed as a 1-dimensional | ||
+ | array of words, although indices are stored with the memory for | ||
+ | calculating a canonical address from a multi-dimensional address. | ||
+ | |||
+ | Three types of memory statement perform (1) creation of a memory, (2) | ||
+ | connecting a read port to an existing memory, and (3) initializing the | ||
+ | memory's contents. | ||
+ | |||
+ | <label> .mem "name", <msb>,<lsb>, <last>,<first> ... ; | ||
+ | |||
+ | The pair of numbers <msb>,<lsb> defines the word width. The pair | ||
+ | <last>,<first> defines the address range. Multiple address ranges are | ||
+ | allowed for multidimensional indexing. This statement creates the | ||
+ | memory array and makes it available to procedural code. | ||
+ | |||
+ | Procedural access to the memory references the memory as single array | ||
+ | of words, with the base address==0, and the last address the size (in | ||
+ | words) of the memory -1. It is up to the compiler to convert Verilog | ||
+ | index sets to a canonical address. The multi-dimensional index set is | ||
+ | available for VPI use. | ||
+ | |||
+ | Structural read access is implemented in terms of address and data | ||
+ | ports. The addresses applied to the address port are expected to be | ||
+ | in canonical form. | ||
+ | |||
+ | A read port is a functor that takes a single input, the read address, | ||
+ | and outputs the word value at the given (canonical) address. | ||
+ | |||
+ | <label> .mem/port <memid>, <address> ; | ||
+ | |||
+ | <label> identifies the vector of output functors, to allow connections | ||
+ | to the data output. <memid> is the label of the memory. | ||
+ | |||
+ | Any address input change, or any change in the addressed memory | ||
+ | contents, is immediately propagated to the port output. | ||
+ | |||
+ | A write port is a superset of a read port. It is a 4-input functor | ||
+ | that accepts the word address, an event input, a write enable input, | ||
+ | and the data input. | ||
+ | |||
+ | <label> .mem/port <memid>, <address>, <event>, <we>, <data> ; | ||
+ | |||
+ | <event> is an event functor that triggers a write, if the <we> input | ||
+ | is true. <data> is the input that connect to the data input | ||
+ | port. For asynchronous transparent write operation, connect | ||
+ | <event> to C4<z>, the RAM will transparently follow any changes on | ||
+ | address and data lines, while <we> is true. | ||
+ | |||
+ | There is no Verilog construct that calls for a structural write port | ||
+ | to a memory, but synthesis may ask for lpm_ram_d[pq] objects. | ||
+ | |||
+ | To initialize a memory, use: | ||
+ | |||
+ | .mem/init <memid> <start>, val , val ... ; | ||
+ | |||
+ | <memid> is the label of the memory, and the <start> is the start | ||
+ | address (canonical) of the first word to be initialized. The start | ||
+ | address allows multiple statements be used to initialize words of a | ||
+ | memory. | ||
+ | |||
+ | The values are one per word. | ||
+ | |||
+ | Procedural access to the memory employs an index register to address a | ||
+ | bit location in the memory, via the commands: | ||
+ | |||
+ | %load/m <bit>, <memid> ; | ||
+ | %set/m <memid>, <bit> ; | ||
+ | %assign/m <memid>, <delay>, <bit> ; | ||
+ | |||
+ | The memory bit is addressed by index register 3. The value of | ||
+ | register 3 is the index in the memory's bit space, where each data | ||
+ | word occupies a multiple of four bits. | ||
+ | |||
+ | |||
+ | EVENT STATEMENTS | ||
+ | |||
+ | Threads need to interact with the functors of a netlist synchronously, | ||
+ | as well as asynchronously. There are cases where the web of functors | ||
+ | needs to wake up a waiting thread. The web of functors signals threads | ||
+ | through .event objects, that are declared like so: | ||
+ | |||
+ | <label> .event <type>, <symbols_list>; | ||
+ | <label> .event "name"; | ||
+ | |||
+ | |||
+ | This event statement declares an object that a %wait instruction | ||
+ | can take as an operand. When a thread executes a %wait, it puts | ||
+ | itself in the notification list of the event and suspends. The | ||
+ | <symbols_list> is a set of inputs that can trigger the event. | ||
+ | |||
+ | The <type> describes the conditions needed to trigger the event. It | ||
+ | may be posedge, negedge or edge. If the type is instead a "name" | ||
+ | string, then this is a named event which receives events by the %set | ||
+ | instruction instead of from the output of a functor. | ||
+ | |||
+ | If the event has inputs (a requirement unless it is a named event) | ||
+ | then it has up to 4 symbols that address functors. The event then | ||
+ | detects the appropriate edge on any of the inputs and signals when the | ||
+ | event is true. Normally (in Verilog) a posedge or negedge event only | ||
+ | watches a single bit, so the generated code would only include a | ||
+ | single symbol for the addressed bit. However, if there are several | ||
+ | events of the same edge in an event OR expression, the compiler may | ||
+ | combine up to 4 into a single event. | ||
+ | |||
+ | If many more events need to be combined together (for example due to | ||
+ | an event or expression in the Verilog) then this form can be used: | ||
+ | |||
+ | <label> .event/or <symbols_list>; | ||
+ | |||
+ | In this case, the symbols list all the events that are to be combined | ||
+ | to trigger this event. Only one of the input events needs to trigger | ||
+ | to make this one go. | ||
+ | |||
+ | |||
+ | RESOLVER STATEMENTS: | ||
+ | |||
+ | Resolver statements are strength-aware functors with 4 inputs, but | ||
+ | their job typically is to calculate a resolved output using strength | ||
+ | resolution. The type of the functor is used to select a specific | ||
+ | resolution function. | ||
+ | |||
+ | <label> .resolv tri, <symbols_list>; | ||
+ | <label> .resolv tri0, <symbols_list>; | ||
+ | <label> .resolv tri1, <symbols_list>; | ||
+ | |||
+ | The output from the resolver is vvp_vector8_t value. That is, the | ||
+ | result is a vector with strength included. | ||
+ | |||
+ | |||
+ | PART SELECT STATEMENTS: | ||
+ | |||
+ | Part select statements are functors with three inputs. They take in at | ||
+ | port-0 a vector, and output a selected (likely smaller) part of that | ||
+ | vector. The other inputs specify what those parts are, as a canonical | ||
+ | bit number, and a width. Normally, those bits are constant values. | ||
+ | |||
+ | <label> .part <symbol>, <base>, <wid>; | ||
+ | <label> .part/pv <symbol>, <base>, <wid>, <vector_wid>; | ||
+ | <label> .part/v <symbol>, <symbol>, <wid>; | ||
+ | |||
+ | The input is typically a .reg or .net, but can be any vector node in | ||
+ | the netlist. | ||
+ | |||
+ | The .part/pv variation is the inverse of the .part version, in that | ||
+ | the output is actually written to a *part* of the output. The node | ||
+ | uses special part-select-write functions to propagate a part of a | ||
+ | network. The <vector_wid> is the total width of the destination net | ||
+ | that part is written to. Destination nodes use this value to check | ||
+ | further output widths. | ||
+ | |||
+ | The .part/v variation takes a vector (or long) input on port-1 as the | ||
+ | base of the part select. Thus, the part select can move around. | ||
+ | |||
+ | PART CONCATENATION STATEMENTS: | ||
+ | |||
+ | The opposite of the part select statement is the part concatenation | ||
+ | statement. The .concat statement is a functor node that takes at input | ||
+ | vector values and produces a single vector output that is the | ||
+ | concatenation of all the inputs. | ||
+ | |||
+ | <label> .concat [W X Y Z], <symbols_list> ; | ||
+ | |||
+ | The "[" and "]" tokens surround a set of 4 numbers that are the | ||
+ | expected widths of all the inputs. These widths are needed to figure | ||
+ | the positions of the input vectors in the generated output, and are | ||
+ | listed in order LSB to MSB. The inputs themselves are also listed LSB | ||
+ | to MSB, with the LSB vector input coming through port-0 of the real | ||
+ | functor. | ||
+ | |||
+ | The initial output value is (W+X+Y+Z) bits of 'bx. As input values are | ||
+ | propagated, the bits are placed in the correct place in the output | ||
+ | vector value, and a new output value is propagated. | ||
+ | |||
+ | |||
+ | REPEAT VECTOR STATEMENTS: | ||
+ | |||
+ | The repeat vector statement is similar to the concatenation statement, | ||
+ | expect that the input is repeated a constant number of times. The | ||
+ | format of the repeat vector statement is: | ||
+ | |||
+ | <label> .repeat <wid>, <rept count>, <symbol> ; | ||
+ | |||
+ | In this statement, the <wid> is a decimal number that is the width of | ||
+ | the *output* vector. The <rept count> is the number of time the input | ||
+ | vector value is repeated to make the output width. The input width is | ||
+ | implicit from these numbers. The <symbol> is then the input source. | ||
+ | |||
+ | REDUCTION LOGIC | ||
+ | |||
+ | The reduction logic statements take in a single vector, and propagate | ||
+ | a single bit. | ||
+ | |||
+ | <label> .reduce/and <symbol> ; | ||
+ | <label> .reduce/or <symbol> ; | ||
+ | <label> .reduce/xor <symbol> ; | ||
+ | <label> .reduce/nand <symbol> ; | ||
+ | <label> .reduce/nor <symbol> ; | ||
+ | <label> .reduce/xnor <symbol> ; | ||
+ | |||
+ | the device has a single input, which is a vector of any width. The | ||
+ | device performs the logic on all the bits of the vector (a la Verilog) | ||
+ | and produces and propagates a single bit width vector. | ||
+ | |||
+ | EXPANSION LOGIC | ||
+ | |||
+ | Sign extension nodes are the opposite of reduction logic, in that they | ||
+ | take a narrow vector, or single bit, and pad it out to a wider | ||
+ | vector. | ||
+ | |||
+ | <label> .expand/s <wid>, <symbol> ; | ||
+ | |||
+ | The .expand/s node takes an input symbol and sign-extends it to the | ||
+ | given width. | ||
+ | |||
+ | FORCE STATEMENTS (old method - remove me): | ||
+ | |||
+ | A force statement creates functors that represent a Verilog force | ||
+ | statement. | ||
+ | |||
+ | <label> .force <signal>, <symbol_list>; | ||
+ | |||
+ | The symbol <signal> represents the signal which is to be forced. The | ||
+ | <symbol_list> specifies the bits of the expression that is to be | ||
+ | forced on the <signal>. The <label> identifies the force functors. | ||
+ | There will be as many force functors as there are symbols in the | ||
+ | <symbol_list>. | ||
+ | |||
+ | To activate and deactivate a force on a single bit, use: | ||
+ | |||
+ | %force <label>, <width>; | ||
+ | %release <signal>; | ||
+ | |||
+ | <label>/<width> is the label/width of a vector of force functors. | ||
+ | <signal> is the label of the functor that drives the signal that is | ||
+ | being forced. | ||
+ | |||
+ | FORCE STATEMENTS (new method - implement me): | ||
+ | |||
+ | A %force instruction, as described in the .var section, forces a | ||
+ | constant value onto a .var or .net, and the matching %release releases | ||
+ | that value. However, there are times when the value of a functor | ||
+ | (i.e. another .net) needs to be forced onto a .var or .net. For this | ||
+ | task, the %force/link instruction exists: | ||
+ | |||
+ | %force/link <dst>, <src> ; | ||
+ | %release/link <dst> ; | ||
+ | |||
+ | This causes the output of the node <src> to be linked to the force | ||
+ | input of the <dst> .var/.net node. When linked, the output functor | ||
+ | will automatically drive values to the force port of the destination | ||
+ | node. The matching %release/link instruction removes the link (a | ||
+ | %release is still needed) to the destination. The %release/link | ||
+ | releases the last %force/link, no matter where the link is from. A new | ||
+ | %force/link will remove a previous link. | ||
+ | |||
+ | The instructions: | ||
+ | |||
+ | %cassign/link <dst>, <src> ; | ||
+ | %deassign/link <dst> ; | ||
+ | |||
+ | are the same concept, but for the continuous assign port. | ||
+ | |||
+ | STRUCTURAL ARITHMETIC STATEMENTS: | ||
+ | |||
+ | The various Verilog arithmetic operators (+-*/%) are available to | ||
+ | structural contexts as two-input functors that take in vectors. All of | ||
+ | these operators take two inputs and generate a fixed width output. The | ||
+ | input vectors will be padded if needed to get the desired output width. | ||
+ | |||
+ | <label> .arith/sub <wid>, <A>, <B>; | ||
+ | <label> .arith/sum <wid>, <A>, <B>; | ||
+ | <label> .arith/mult <wid>, <A>, <B>; | ||
+ | <label> .arith/div <wid>, <A>, <B>; | ||
+ | <label> .arith/mod <wid>, <A>, <B>; | ||
+ | |||
+ | In all cases, there are no width limits, so long as the width is | ||
+ | fixed. | ||
+ | |||
+ | NOTE: The .arith/mult inputs are not necessarily the width of the | ||
+ | output. I have not decided how to handle this. | ||
+ | |||
+ | These devices support .s and .r suffixes. The .s means the node is a | ||
+ | signed vector device, the .r a real valued device. | ||
+ | |||
+ | STRUCTURAL COMPARE STATEMENTS: | ||
+ | |||
+ | The arithmetic statements handle various arithmetic operators that | ||
+ | have wide outputs, but the comparators have single bit output, so they | ||
+ | are implemented a bit differently. The syntax, however, is very | ||
+ | similar: | ||
+ | |||
+ | <label> .cmp/eeq <wid>, <A>, <B>; | ||
+ | <label> .cmp/nee <wid>, <A>, <B>; | ||
+ | <label> .cmp/eq <wid>, <A>, <B>; | ||
+ | <label> .cmp/ne <wid>, <A>, <B>; | ||
+ | <label> .cmp/ge <wid>, <A>, <B>; | ||
+ | <label> .cmp/gt <wid>, <A>, <B>; | ||
+ | <label> .cmp/ge.s <wid>, <A>, <B>; | ||
+ | <label> .cmp/gt.s <wid>, <A>, <B>; | ||
+ | |||
+ | Whereas the arithmetic statements generate an output the width of | ||
+ | <wid>, the comparisons produce a single bit vector result. The plain | ||
+ | versions do unsigned comparison, but the ".s" versions to signed | ||
+ | comparisons. (Equality doesn't need to care about sign.) | ||
+ | |||
+ | |||
+ | STRUCTURAL SHIFTER STATEMENTS: | ||
+ | |||
+ | Variable shifts in structural context are implemented with .shift | ||
+ | statements: | ||
+ | |||
+ | <label> .shift/l <wid>, <data symbol>, <shift symbol>; | ||
+ | <label> .shift/r <wid>, <data symbol>, <shift symbol>; | ||
+ | |||
+ | The shifter has a width that defines the vector width of the output, a | ||
+ | <data symbol> that is the input data to be shifted and a <shift-symbol> | ||
+ | that is the amount to shift. The vectors that come from port 0 are the | ||
+ | data to be shifted and must have exactly the width of the output. The | ||
+ | input to port 1 is the amount to shift. | ||
+ | |||
+ | |||
+ | STRUCTURAL FUNCTION CALLS: | ||
+ | |||
+ | The .ufunc statement defines a call to a user defined function. | ||
+ | |||
+ | <label> .ufunc <flabel>, <wid>, <isymbols> ( <psymbols> ) <rsymbol> ; | ||
+ | |||
+ | The <flabel> is the code label for the first instruction of the | ||
+ | function implementation. This is code that the simulator will branch | ||
+ | to. | ||
+ | |||
+ | The <wid> is the width of the output vector in bits. | ||
+ | |||
+ | The <isymbols> is a list of net symbols for each of the inputs to the | ||
+ | function. These are points in the net, and the ufunc device watches | ||
+ | these nets for input changes. | ||
+ | |||
+ | The <psymbols> list is exactly the same size as the <isymbols> | ||
+ | list. The <psymbols> are variables that represent the input ports for | ||
+ | the function. The ufunc performs an assignment to these variables | ||
+ | before calling the function. | ||
+ | |||
+ | Finally, the <rsymbol> is the variable within the function where the | ||
+ | result will be found when the function code ends. This value is picked | ||
+ | up and propagated to the output of the functor. | ||
+ | |||
+ | THREAD STATEMENTS: | ||
+ | |||
+ | Thread statements create the initial threads for a simulation. These | ||
+ | represent the initial and always blocks, and possibly other causes to | ||
+ | create threads at startup. | ||
+ | |||
+ | .thread <symbol> [, <flag>] | ||
+ | |||
+ | This statement creates a thread with a starting address at the | ||
+ | instruction given by <symbol>. When the simulation starts, a thread is | ||
+ | created for the .thread statement, and it starts at the <symbol> | ||
+ | addressed instruction. | ||
+ | |||
+ | The <flag> modifies the creation/execution behavior of the | ||
+ | thread. Supported flags are: | ||
+ | |||
+ | $push -- Cause the thread to be pushed in the scheduler. This | ||
+ | only effects startup (time 0) by arranging for pushed | ||
+ | threads to be started before non-pushed threads. This | ||
+ | is useful for resolving time-0 races. | ||
+ | |||
+ | * Threads in general | ||
+ | |||
+ | Thread statements create the initial threads of a design. These | ||
+ | include the ``initial'' and ``always'' statements of the original | ||
+ | Verilog, and possibly some other synthetic threads for various | ||
+ | purposes. It is also possible to create transient threads from | ||
+ | behavioral code. These are needed to support such constructs as | ||
+ | fork/join, named blocks and task activation. | ||
+ | |||
+ | A transient thread is created with a %fork instruction. When a | ||
+ | transient thread is created this way, the operand to the %fork gives | ||
+ | the starting address, and the new thread is said to be a child of the | ||
+ | forking thread. The children of a thread are pushed onto a stack of | ||
+ | children. A thread can have only one direct child. | ||
+ | |||
+ | A transient thread is reaped with a %join instruction. %join waits for | ||
+ | the top thread in the stack of children to complete, then | ||
+ | continues. It is an error to %join when there are no children. | ||
+ | |||
+ | As you can see, the transient thread in VVP is a cross between a | ||
+ | conventional thread and a function call. In fact, there is no %call | ||
+ | instruction in vvp, the job is accomplished with %fork/%join in the | ||
+ | caller and %end in the callee. The %fork, then is simply a | ||
+ | generalization of a function call, where the caller does not | ||
+ | necessarily wait for the callee. | ||
+ | |||
+ | For all the behavior of threads and thread parentage to work | ||
+ | correctly, all %fork statements must have a corresponding %join in the | ||
+ | parent, and %end in the child. Without this proper matching, the | ||
+ | hierarchical relationships can get confused. The behavior of erroneous | ||
+ | code is undefined. | ||
+ | |||
+ | * Thread Context | ||
+ | |||
+ | The context of a thread is all the local data that only that thread | ||
+ | can address. The local data is broken into two addresses spaces: bit | ||
+ | memory and word memory. | ||
+ | |||
+ | The bit memory is a region of 4-value bits (0,1,x,z) that can be | ||
+ | addressed in strips of arbitrary length. For example, an 8-bit value | ||
+ | can be in locations 8 through and including 15. The bits at address 0, | ||
+ | 1, 2 and 3 are special constant values. Reads from those locations | ||
+ | make vectors of 0, 1, x or z values, so these can be used to | ||
+ | manufacture complex values elsewhere. | ||
+ | |||
+ | The word memory is a region of tagged words. The value in each word | ||
+ | may be native long or real. These words have a distinct address space | ||
+ | from the bits. | ||
+ | |||
+ | * Threads and scopes | ||
+ | |||
+ | The Verilog ``disable'' statement deserves some special mention | ||
+ | because of how it interacts with threads. In particular, threads | ||
+ | throughout the design can affect (end) other threads in the design | ||
+ | using the disable statement. | ||
+ | |||
+ | In Verilog, the operand to the disable statement is the name of a | ||
+ | scope. The behavior of the disable is to cause all threads executing | ||
+ | in the scope to end. Termination of a thread includes all the children | ||
+ | of the thread. In vvp, all threads are in a scope, so this is how the | ||
+ | disable gains access to the desired thread. | ||
+ | |||
+ | It is obvious how initial/always thread join a scope. They become part | ||
+ | of the scope simply by being declared after a .scope declaration. (See | ||
+ | vvp.txt for .scope declarations.) The .thread statement placed in the | ||
+ | assembly source after a .scope statement causes the thread to join the | ||
+ | named scope. | ||
+ | |||
+ | Transient threads join a scope that is the operand to the %fork | ||
+ | instruction. The scope is referenced by name, and the thread created | ||
+ | by the fork atomically joins that scope. Once the transient thread | ||
+ | joins the scope, it stays there until it ends. Threads never change | ||
+ | scopes, not even transient threads. | ||
+ | |||
+ | TRUTH TABLES | ||
+ | |||
+ | The logic that a functor represents is expressed as a truth table. The | ||
+ | functor has four inputs and one output. Each input and output has one | ||
+ | of four possible values (0, 1, x and z) so two bits are needed to | ||
+ | represent them. So the input of the functor is 8 bits, and the output | ||
+ | 2 bits. A complete lookup table for generating the 2-bit output from | ||
+ | an 8-bit input is 512 bits. That can be packed into 64 bytes. This is | ||
+ | small enough that the table should take less space then the code to | ||
+ | implement the logic. | ||
+ | |||
+ | To implement the truth table, we need to assign 2-bit encodings for | ||
+ | the 4-value signals. I choose, pseudo-randomly, the following | ||
+ | encoding: | ||
+ | |||
+ | 1'b0 : 00 | ||
+ | 1'b1 : 01 | ||
+ | 1'bx : 10 | ||
+ | 1'bz : 11 | ||
+ | |||
+ | The table is an array of 64 bytes, each byte holding 4 2-bit | ||
+ | outputs. Construct a 6-bit byte address with inputs 1, 2 and 3 like | ||
+ | so: | ||
+ | 332211 | ||
+ | |||
+ | The input 0 2-bits can then be used to select which of the 4 2-bit | ||
+ | pairs in the 8-bit byte are the output: | ||
+ | |||
+ | MSB -> zzxx1100 <- LSB | ||
+ | |||
+ | A complete truth table, then is described as 64 8-bit bytes. | ||
+ | |||
+ | The vvp engine includes truth tables for the primitive gate types, so | ||
+ | none needs to be given by the programmer. It is sufficient to name the | ||
+ | type to get that truth table. | ||
+ | |||
+ | |||
+ | EXECUTABLE INSTRUCTIONS | ||
+ | |||
+ | Threads run executable code, much like a processor executes machine | ||
+ | code. VVP has a variety of opcodes for executable instructions. All of | ||
+ | those instructions start with '%' and go into a single address | ||
+ | space. Labels attached to executable instructions get assigned the | ||
+ | address of the instruction, and can be the target of %jmp instructions | ||
+ | and starting points for threads. | ||
+ | |||
+ | The opcodes.txt file has a more detailed description of all the | ||
+ | various instructions. | ||
+ | |||
+ | |||
+ | THE RELATIONSHIP BETWEEN FUNCTORS, THREADS AND EVENTS | ||
+ | |||
+ | Given the above summary of the major components of vvp, some | ||
+ | description of their relationship is warranted. Functors provide a | ||
+ | structural description of the design (so far as it can be described | ||
+ | structurally) and these functors run independently of the threads. In | ||
+ | particular, when an input to a functor is set, it calculates a new | ||
+ | output value; and if that output is different from the existing | ||
+ | output, a propagation event is created. Functor output is calculated | ||
+ | by truth table lookup, without the aid of threads. | ||
+ | |||
+ | Propagation events are one of three kinds of events in vvp. They are | ||
+ | scheduled to execute at some time, and they simply point to the functor | ||
+ | that is to have its output propagated. When the event expires, the | ||
+ | output of the referenced functor is propagated to all the inputs that | ||
+ | it is connected to, and those functors in turn create new events if | ||
+ | needed. | ||
+ | |||
+ | Assignment events (the second of three types of events) are created | ||
+ | by non-blocking assignments in behavioral code. When the ``<='' is | ||
+ | executed (a %assign in vvp) an assign event is created, which includes | ||
+ | the vvp_ipoint_t pointer to the functor input to receive the value, | ||
+ | as well as the value. These are distinct from propagation events because: | ||
+ | |||
+ | a) There is no functor that has as its output the value to be | ||
+ | assigned (this is how values get into the functor net in | ||
+ | the first place), and | ||
+ | |||
+ | b) This allows for behavioral code to create waveforms of | ||
+ | arbitrary length that feed into a variable. Verilog allows | ||
+ | this of non-blocking assignments, but not of gate outputs. | ||
+ | |||
+ | The last type of event is the thread schedule event. This event simply | ||
+ | points to a thread to be executed. Threads are made up of a virtual | ||
+ | processor with a program counter and some private storage. Threads | ||
+ | can execute %assign instructions to create assignment events, and can | ||
+ | execute %set instructions to do blocking assignments. Threads can also | ||
+ | use %load to read the output of functors. | ||
+ | |||
+ | The core event scheduler takes these three kinds of events and calls | ||
+ | the right kind of code to cause things to happen in the design. If the | ||
+ | event is a propagate or assignment event, the network of functors is | ||
+ | tickled; if the event is a thread schedule, then a thread is run. The | ||
+ | implementation of the event queue is not important, but currently is | ||
+ | implemented as a ``skip list''. That is, it is a sorted singly linked | ||
+ | list with skip pointers that skip over delta-time events. | ||
+ | |||
+ | The functor net and the threads are distinct. They communicate through | ||
+ | thread instructions %set, %assign, %waitfor and %load. So far as a thread | ||
+ | is concerned, the functor net is a blob of structure that it pokes and | ||
+ | prods via certain functor access instructions. | ||
+ | |||
+ | |||
+ | VVP COMPILATION AND EXECUTION | ||
+ | |||
+ | The vvp program operates in a few steps: | ||
+ | |||
+ | 1) Initialization | ||
+ | Data structures are cleared to empty, and tables are | ||
+ | readied for compilation. | ||
+ | |||
+ | 2) Compilation | ||
+ | The input file is read and compiled. Symbol tables are | ||
+ | build up as needed, objects are allocated and linked | ||
+ | together. | ||
+ | |||
+ | 3) Cleanup | ||
+ | Symbol tables and other resources used only for | ||
+ | compilation are released to reduce the memory | ||
+ | footprint. | ||
+ | |||
+ | 4) Simulation | ||
+ | Event simulation is run. | ||
+ | |||
+ | |||
+ | The initialization step is performed by the compile_init() function in | ||
+ | compile.cc. This function in turn calls all the *_init() functions in | ||
+ | other parts of the source that need initialization for compile. All | ||
+ | the various sub-init functions are called <foo>_init(). | ||
+ | |||
+ | Compilation is controlled by the parser, it parse.y. As the parser | ||
+ | reads and parses input, the compilation proceeds in the rules by | ||
+ | calling various compile_* functions. All these functions live in the | ||
+ | compile.cc file. Compilation calls other sections of the code as | ||
+ | needed. | ||
+ | |||
+ | When the parser completes compilation, compile_cleanup() is called to | ||
+ | finish the compilation process. Unresolved references are completed, | ||
+ | then all the symbol tables and other compile-time-only resources are | ||
+ | released. Once compile_cleanup() returns, there is no more use for the | ||
+ | parser for the function in compile.cc. | ||
+ | |||
+ | After cleanup, the simulation is started. This is done by executing | ||
+ | the schedule_simulate() function. This does any final setup and starts | ||
+ | the simulation running and the event queue running. | ||
+ | |||
+ | |||
+ | HOW TO GET FROM THERE TO HERE | ||
+ | |||
+ | The vvp simulation engine is designed to be able to take as input a | ||
+ | compiled form of Verilog. That implies that there is a compiler that | ||
+ | compiles Verilog into a form that the vvp engine can read. | ||
+ | |||
+ | |||
+ | * Boolean logic gates | ||
+ | |||
+ | Gates like AND, OR and NAND are implemented simply and obviously by | ||
+ | functor statements. Any logic up to 4 inputs can be implemented with a | ||
+ | single functor. For example: | ||
+ | |||
+ | and gate (out, i1, i2, i3); | ||
+ | |||
+ | becomes: | ||
+ | |||
+ | gate .functor and, i1, i2, i3; | ||
+ | |||
+ | Notice the first parameter of the .functor is the type. The type | ||
+ | includes a truth table that describes the output with a given | ||
+ | input. If the gate is wider then four inputs, then cascade | ||
+ | functors. For example: | ||
+ | |||
+ | and gate (out, i1, i2, i3, i4, i5, i6, i7, i8); | ||
+ | |||
+ | becomes: | ||
+ | |||
+ | gate.0 .functor and, i1, i2, i3, i4; | ||
+ | gate.1 .functor and, i5, i6, i7, i8; | ||
+ | gate .functor and, gate.0, gate.1; | ||
+ | |||
+ | |||
+ | * reg and other variables | ||
+ | |||
+ | Reg and integer are cases of what Verilog calls ``variables.'' | ||
+ | Variables are, simply put, things that behavioral code can assign | ||
+ | to. These are not the same as ``nets,'' which include wires and the | ||
+ | like. | ||
+ | |||
+ | Each bit of a variable is created by a ``.var'' statement. For example: | ||
+ | |||
+ | reg a; | ||
+ | |||
+ | becomes: | ||
+ | |||
+ | a .var "a", 0, 0; | ||
+ | |||
+ | |||
+ | * named events | ||
+ | |||
+ | Events in general are implemented as functors, but named events in | ||
+ | particular have no inputs and only the event output. The way to | ||
+ | generate code for these is like so: | ||
+ | |||
+ | a .event "name"; | ||
+ | |||
+ | This creates a functor and makes it into a mode-2 functor. Then the | ||
+ | trigger statement, "-> a", cause a ``%set a, 0;'' statement be | ||
+ | generated. This is sufficient to trigger the event. | ||
+ | |||
+ | /* | ||
+ | * Copyright (c) 2001 Stephen Williams (steve@icarus.com) | ||
+ | * | ||
+ | * This source code is free software; you can redistribute it | ||
+ | * and/or modify it in source code form under the terms of the GNU | ||
+ | * General Public License as published by the Free Software | ||
+ | * Foundation; either version 2 of the License, or (at your option) | ||
+ | * any later version. | ||
+ | * | ||
+ | * This program is distributed in the hope that it will be useful, | ||
+ | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
+ | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
+ | * GNU General Public License for more details. | ||
+ | * | ||
+ | * You should have received a copy of the GNU General Public License | ||
+ | * along with this program; if not, write to the Free Software | ||
+ | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
+ | */</code> |