Introduction to Abstract Verilog Abstract Writing Verilog programs to describe most circuits is harder and more confusing that it needs to be. Abstract Verilog extends the Verilog language with straightforward constructs that replace many confusing idioms that are used in Verilog for describing synthesizable designs. AbstractVerilog defines a clean way to declare and use registered and combinational signals that makes it easy to write and understand programmed descriptions of hardware. AbstractVerilog allows designers to write hardware modules without knowing about many of the more complicated features of Verilog, and keeps them from writing the programs that have lots of non-obvious semantic errors. AbstractVerilog assumes a straightforward, synchronous design style. For example, in the typical case, every module with a register has a clock and reset input, and all registers are positive edge-triggered. (This is easily extended to multiple clocks and negative-edge triggered registers.) We assume that the reader already knows how to design hardware and is familiar with the rudiments of Verilog such as port declarations and Verilog syntax for expressions and statements like if-then-else, case and for. Introduction A module in Verilog, such as the one shown in Figure 1, represents a component in a larger hardware design. Modules are instantiated and connected to other modules in a hierarchical manner using schematics or structural Verilog. Primitive modules do not instantiate other modules, but instead describe the complete functionality of that module in terms of combinational and sequential logic. Modules are connected via ports, which are input, output or bi-directional signals. The module port list contains a list of these signals, which are then declared as inputs, outputs or inouts (bi-directional) in the body of the module. Abstract Verilog is used to describe the functionality of components in terms of combinational and registers. For example, state machines are made using registers to remember the current state and combinational logic to compute the next state and the state machine outputs. All signals are classified as combinational or registered. Combinational signals are computed by combinational logic and change (potentially) whenever an input changes, whereas registered signals are the outputs of registers and change only on a clock edge. A S FA B Cout Cin Figure 1: Full-adder component // Simple fulladder module fulladder(S, Cout, A, B, Cin); input A, B, Cin; output S, Cout; COMB S, Cout; ALWAYS begin { Cout, S } = A + B + Cin; end endmodule Signals in Abstract Verilog are thus declared to be combinational (COMB) or registered (REGISTER). The syntax otherwise conforms exactly to Verilog, with some extensions. Here are some examples of COMB signal declarations: COMB [3:0] y; COMB enable; COMB dataValid = (rp != wp); Y is a 4-bit bus while enable is a simple wire. The dataValid signal is defined to always be the result of the expression (rp != wp). If a COMB signal is defined in the declaration, then it cannot be assigned a value elsewhere in the module. Here are some examples of REGISTER signal declarations: REGISTER [2:0] state = IDLE; REGISTER(clk,reset) foo; REGISTER(clk2) bar; REGISTER_NEG face; Note that registers can be declared with an initial value, which is assigned to the register at reset. If no initial value is given, then no initial value is assigned to the register. If any REGISTER signals are declared, then the module must define signals called “clk” and “reset” by default. The REGISTER declaration can specify the name of the clock and reset signal. If no reset is given, as in the case of bar above, then the register is cannot be assigned an initial value in the declaration. REGISTER_NEG declares a register that operates on the negative edge of the clock.1 Note that input ports of a module are not declared as COMB or REGISTER – they are declared elsewhere. However, all output and inout signals must be declared along with all internal signals. Combinational and registered signals are assigned values in an ALWAYS block, which is continuously executed. The ALWAYS block is evaluated whenever a signal changes that is an input of the ALWAYS block, that is, a signal that is used to determine the value of some signal. Statements in an ALWAYS block are executed sequentially just as in the Verilog always block. Statements execute in zero time: This means that even though the statements execute in order, they take no time to execute. COMB signals change value when they are assigned, and in Verilog. REGISTER signal assignments have no effect until the next clock edge for that register. COMB signals are assigned using the = operator. REGISTER signals are assigned values using the <-- operator. Register assignments are remembered but do not take effect until the next clock “tick” (rising edge). This means that all sequential assignments take place simultaneously at the clock edge. For example, the following code: a <-- b; b <-- a; swaps the values of a and b when the clock next ticks. Note, moreover, that in the following code, the count register value is incremented by 1, not 2, since the first assignment is not executed until the next clock tick, and thus the second assignment uses the current, unchanged, value of count and overwrites the first assignment. count <-- count + 1; . . . count <-- count + 1; Combinational signals take newly assigned values immediately, while register signals do not change until the next clock tick. If a combinational output of an ALWAYS block is also an input to the same ALWAYS block, then the ALWAYS block may be infinitely re-executed in the same simulation step. Combinational feedback is of course forbidden. (Multiple ALWAYS blocks are allowed in a module for clarity, but it is illegal for more than one ALWAYS block to assign values to the same signal.) As in ordinary synthesizable Verilog, all combinational signals that are assigned in the ALWAYS block must be assigned a value for any execution of the block. A compiler flag can be used to force Abstract Verilog to assign all COMB signals to ‘X’ at the beginning of the ALWAYS block. If a signal is not assigned, then it will show up as an ‘X’ in simulation. However, it is safer to use a good Verilog compiler to do this check. For example, Synplify synthesis will warn of inserted latches, although Aldec will not. Having two different assignment operators is redundant since all signals are declared as either COMB or REGISTER, but their use makes the code much clearer. 1 Negative edge-triggered registers are not yet implemented. Example Combinational Circuit Program You have already seen one program, for a full-adder, in Figure 1. The following program implements a simple decoder. // Implement a 3:8 decoder module decoder (in, out); input [2:0] in; output [7:0] out; COMB [7:0] out; ALWAYS begin case (in) 0: out 1: out 2: out 3: out 4: out 5: out 6: out 7: out endcase end = = = = = = = = 8’b00000001; 8’b00000010; 8’b00000100; 8’b00001000; 8’b00010000; 8’b00100000; 8’b01000000; 8’b10000000; endmodule Here is a more complicated program for the game of life that uses other interesting Verilog constructs. Note that integer declares a compile-time variable used to compute the value of “real” signals. module life (neighbors, self, out); input self; input [7:0] neighbors; output out; COMB out; integer count; integer i; ALWAYS begin count = 0; for (i = 0; i<8; i = i+1) count = count + neighbors[i]; out = 0; out = out | (count == 3); out = out | ((self == 1) & (count == 2)); end endmodule Example Sequential Circuit Program Here is a simple Abstract-Verilog program that implements a counter. Note that the assignment to tc could be done at the end of the ALWAYS block since any assignment to vcount does not occur until the next clock edge. That is, even after the assignment statement vcount <-- vcount + 1; is executed, the value of vcount remains unchanged until the next clock edge. In fact, the ALWAYS block could be executed several times without changing the value of vcount. The value of vcount is changed implicitly when the next clock edge occurs. module countv (clk, reset, cen, vcount, tc); parameter limit = 666; input clk; input reset; input cen; output [9:0] vcount; output tc; // Terminal count REGISTER [9:0] vcount = 0; COMB tc = (vcount == limit); // Assert terminal count when vcount has reached the limit ALWAYS begin if (cen) begin if (vcount < limit) begin vcount <-- vcount + 1; end else begin vcount <-- 0; end end end endmodule Running Abstract Verilog The Abstract Verilog translator is a Java program that is run on Windows machines using the rv.bat command file. It assumes that the O: drive has the ntdfs\\cs filesystem mounted, and runs programs in the courses\cse467\AstractVerilog folder. rv.bat runs a preprocessor, which processes `defines, then calls the Java translator. We use the .rv suffix for AbstractVerilog programs. Compiling the AbstractVerilog program foo.rv generates the Verilog file foo.v. This output Verilog file can then be simulated and synthesized using the usual Verilog tools. The following statement will compile the two AbstractVerilog programs and generate two corresponding Verilog programs. rv foo.rv bar.rv The rv.bat file can be edited easily (see next page) to use a different version of Java, to not delete the foo.pp.rv preprocessed output file, and to force assignment of ‘X to COMB signals. I find that using emacs to edit files is convenient, and that using a shell window in emacs makes it easy to run the rv command. If someone figures out how to insert this into the Aldec workflow, let me know! But it’s probably more trouble than it’s worth. rv.bat file @echo off rem Translate one or more RVerilog files rem Usage rv foo.rv bar.rv ... rem Files are in the standard network directory set rlogdir=O:\cse\courses\cse467\AbstractVerilog\ set javaVersion=5 :loop rem Check whether file exists if not exist %1 ( echo File %1 missing goto skip ) rem Run the preprocessor perl %rlogdir%\verilog-define-pp.pl %1 > %~n1.pp.rv rem Run the translator rem java -classpath %rlogdir%;%rlogdir%\antlr.jar Main %~n1.pp.rv rem -coff Turns off the assignment of X to combinational signals java -jar %rlogdir%RV-v%javaVersion%.jar -coff %~n1.pp.rv rem Rename output file, leave the preprocessed file for debugging copy/Y %~n1.pp.v %~n1.v del %~n1.pp.v rem Delete the .pp.rv preprocessed file del %~n1.pp.rv :skip shift if "%1"=="" goto done goto loop :done