CDA 4150 Computer Architecture
| |
$dump
which saves all changes to the variables you specify
to a file so that they can be later viewed with an external graphical wave
viewer called GtkWave. A GtkWave manual is also
on-line.vbs -C vbs.conf *.vIn this case, vbs will read in all the Verilog files in your current directory (*.v) and start the simulation. The
-C vbs.conf
option tells vbs where it can read its configuration file that specifies
what Verilog->C extensions are currently supported. For this class, you
will simply need to use gmake
(GNU make) since we will provide you with a
Makefile that will run the vbs -C vbs.conf *.v
command as well
as do a few other useful things. So simply running gmake
will
run the CDA 4150 Verilog Simulator. If you have added any $dump
commands, then running gmake DUMP=1
will run the Verilog
simulator and then bring up GtkWave for viewing the signals when the
simulation is finished. always
blocks with simple assign
statements.
Remember that the left-hand side of an assign
statement is
always a wire
not a reg
. Here are some simple
combinational constructs coded as assign statements:assign c = a & b; // 2-input AND gate assign d = a & b & e & f; // 4-input AND gate assign x = (a ^ b) | d; // a XOR b, ORed with d // 2-input MUX. If sel=1, muxOut = a, else muxOut = 0. assign muxOut = (sel) ? a : 1'b0; // == or != return 1 bit. This assigns a 1 or a 0 // to instIsBranch depending on the comparison result assign instIsBranch = ~(PCsel == `select_pc_inc); // the above could also be assign instIsBranch = (PCsel != `select_pc_inc);For purposes of this class, we will treat combinational logic as having zero delay. We will be focusing on design rather than timing. Typically the next step after the Verilog design is complete is called design synthesis, and there are tools (a popular one is called Synopsys) that can automatically synthesize the behavioral code shown above into gates. To use Synopsys you must specify a design library of parts in the technology you are using for your chip. Once your Verilog is synthesized, you can run timing analysis on it to ensure that it meets spec. In CDA 4150 we will be doing the functional design only (in the interest of time), and leave synthesis and timing verification for another day.
assign
statements. It is more convenient to implement
some statements
using either a case
or casex
statement, or in some
instances using if, else
constructs. You may use these
constructs for combinational logic, but there are some potential gotchas.always
blocks. always
block must appear in the
sensitivity list of the always
block. The only exceptions here are
signals that are also generated in the always
block (are on the left-hand
side). Why is this a requirement? Well, Verilog only evaluates always
blocks when a signal in the sensitivity list changes. If there is a signal
on the right-hand side that is not in the sensitivity list, then even though
that signal changes and you think some left-hand side signal should
therefore change, Verilog will not re-evaluate the always
block and
therefore will not change the value of the left-hand side signal. This can
be a very insidious bug. Please be sure you understand this potential
problem and take extra care each time you use combinational always
blocks.
always
block, especially those that use the if,
else
construct. What does it mean to imply a latch? Since this is a
combinational always
block, each left-hand side signal
MUST be assigned in every possible case through the always
block. If
it is not assigned to in one of the cases, then you have "implied a latch"
because there is some set of inputs for which this gate does not take on a
new value and therefore "holds" its old value. This is no longer a
combinational logic element (it is now a sequential one) and it is almost
certainly not what you really meant to do. BEWARE. This problem and the
previous problem are probably the two most common Verilog bugs. Be extra
careful whenever you create combinational always
blocks and
watch for these gotchas.always
blocks should still use the single
=
sign for assignment. We will see that sequential
always
blocks use a different assignment method.always
blocks. See if you can tell whether they are correct or not:always @(sig1 or sel or sig2) begin if (sel == 1'b1) begin foo = sig1; end else begin foo = sig2; end endThe above is a correct combinational logic block. All right-hand side signals are in the sensitivity list (
sel
is considered a
"right-hand side" signal obviously), and all left-hand side signals are
assigned to in each case.always @(test or PC or AluOut) begin if (select) begin output = PC; end else begin output = AluOut; end endThe above is wrong because
select
does not appear in the
sensitivity list. test
does but is not needed. This is not
strictly an error, but it may slow down your simulation unnecessarily if
test
changes a lot.always @(select or PC or AluOut or IR1 or add3) begin if (select == 2'h0) begin output = PC; output2 = add3 end else if (select == 2'h3) begin output = AluOut; output2= IR1; end endThis is also wrong because
select
is now a 2-bit signal and therefore can
take on 2 other values (2'h1 and 2'h2). Therefore you have implied a latch
on both output
and output2
whenever select
is either 1 or 2! How about this fix:always @(select or PC or AluOut or IR1 or add3 or output or output2) begin if (select == 2'h0) begin output = PC; output2 = add3 end else if (select == 2'h3) begin output = AluOut; output2= IR1; end else begin output = output; output2 = output2; end endThe else condition covers both cases above, and we have also added the right signals to the sensitivity list, so all should be well.
always
block in
behavioral Verilog code. A simple flop looks like this:always @(posedge CLK) begin Q <= `TICK D; Z <= `TICK nextZ; endThis is an example of a positive edge triggered D flip flop. For flops, the only thing that needs to be in the sensitivity list is the clock name and either
posedge
for a positive-edge triggered device or
negedge
for a negative-edge triggered device. It is typically
a bad idea to mix positive and negative edge-triggered devices in the same
design, so we will use almost exclusively posedge triggered flops. Note
that CLK is not a Verilog keyword. It is simply the name of the signal that
you are using as the clock input to this particular flop.<=
sequence to assign values rather than the =
used by combinational logic. The <=
is called a
non-blocking assignment in Verilog. Coupled with the use of `TICK,
this means that for all statements in the always
block, the
right hand sides are sampled at the positive edge of the clock, and then
`TICK units later, the outputs change. Had we used a normal =
sign, Q would still be set to D `TICK cycles after the CLK rises, but
Z
would get nextZ
`TICK cycles after
Q
changes! This is not what we want. We want all right-hand
side elements to be sampled at the rising edge of the clock, and then `TICK
cycles later, have those values appear on the output of the flop. That is
what happens if you use the non-blocking assignments and `TICK as shown
above.always
block like this:always @(posedge CLK) begin Q <= `TICK D; Z <= `TICK (a ^ b) & (sig1 | sig2); end // or always @(posedge CLK) begin if (reset) begin PC <= `TICK 32'h0; end else begin if (stall) begin foo <= `TICK 1; end else begin PC <= `TICK nextPC; end end endBelieve it or not, this is legal Verilog. Unlike combinational logic, in a sequential
always
block, the left-hand side does not
have to appear in every case. It is okay to "imply a latch" here because
this *is* a latch (or a flop)! What you are doing here is essentially
saying that foo
is an enabled flop that gets the value 1 only
when the CLK rises and reset
is 0 and stall is
1. For simplicity, I prefer to do as much combinational logic outside the
always block as possible, and have very simple sequential logic blocks.
This makes the code more readable. Here is how I would rewrite the first
always
block above:wire nextZ; assign nextZ = (a ^ b) & (sig1 | sig2); always @(posedge CLK) begin Q <= `TICK D; Z <= `TICK nextZ; end
`define
statement. Instead of lacing your code with
hardwired constants all over the place, use meaningful names. For example,
the MIPS processor uses `define
statements to define
instruction opcodes, like follows:`define SLL 6'b000000 `define SRL 6'b000010 `define SRA 6'b000011 `define SLLV 6'b000100 `define SRLV 6'b000110 `define SRAV 6'b000111Then later on when these values are needed in the Verilog, you can write code that looks like:
if (opcode == `SLL) begin ... endThis is much more readable and debuggable than having written:
if (opcode == 6'b000000) begin ... endLast, declare all your wires and regs at the top of the file rather than in the middle of your code somewhere, and put a comment there saying what each one of them is for. When writing a new module, group all your inputs together and all your outputs together. Use the MIPS code handed out in lab1 as an example.
$
sign. These commands are actually implemented
as C routines via the CDA 4150 Verilog->C interface
described in the next section. The currently supported extensions are:$seed_mm
can either be used with no
argument, or with a specified argument, as shown below:initial begin $seed_mm; end // or initial begin $seed_mm("test/hello"); endIn the former case, the user will be prompted for the name of the image file when the Verilog simulator first begins. In the latter case, the simulator will just use the name of the specified file as the initial image file for main memory.
$load_mm
will just ignore the lower 2 bits and treat them as 0 anyway. Here is an
example usage:always @(Iaddr) begin Iin = $load_mm (Iaddr); end
$load_mm
. Example usage is:always @(Dwrite or Daddr) begin if (Dwrite) begin if (Dsize == `SIZE_WORD) begin $store_mm (Daddr, Dout); end end end
$disasm(Inst);
), treats that argument as a MIPS instruction,
and prints out the text version of the dissassembled instruction like it
might appear in a MIPS assembly language file. $disasm
is
incredibly useful for debugging the instruction fetch unit of a processor to
make sure it is fetching the proper instructions.
$dump
statements act like $monitor
statements and track changes to
the signals in the dump list. The changes are sent to a file on disk for
later viewing with the external graphical viewer GtkWave. The
$dump
command should be used inside of initial
blocks and you may have as many of them as you want. Be careful of dumping
too many signals for too long a period or your dump files will become
excessively large. An example usage of $dump
is:initial begin $dump(0, 4000, State, RSaddr, RTaddr); end
sysfunc.h
of the MIPS model handed out in
lab1. They allow the Verilog MIPS model to properly handle the syscall
instruction, which normally traps into the operating system to perform
functions like printing to the terminal, opening files, writing files, and
exiting programs. Our Verilog model decodes the syscall instruction, but
executes it by copying all the Verilog registers into a C syscall emulator,
emulating the syscall in C (which has the effect of either printing to the
actual screen or really opening a file on the host machine e.g.) and then
copying the C register file back into Verilog. It is this set of routines
that allows us to run real MIPS C programs unmodified on our CDA 4150 Verilog
Simulator. It is doubtful you will ever have to modify or otherwise know
about what these routines do. They are mentioned here only for the sake of completeness.
vbs.conf
file. Part of the standard vbs.conf
file is shown below:
The first non-comment line is# a comment N1 Llibvbs.so Tdisasm display_insn Fload_mm load_mm
N1
. This specifies that the
C routines to be read in at runtime are all in one library. If you
have multiple libraries (when you use the system library as well as
write your own, for instance) this line will have to be changed to
specify the number of libraries to be used.
The line Llibvbs.so
specifies the filename for the
currently selected library. The library is called
libvbs.so
, and the dynamic linker will attempt to locate
it in the standard library path. If that fails, the linker will look
for the file in a path specified by LD_LIBRARY_PATH
.
The line Tdisasm display_insn
states that the
Verilog system task $disasm
is mapped to the C function
display_insn
from the last selected library (in this
case, libvbs.so
). The T
specifies that this
mapping is to a task rather than a function. Tasks do
not return values, whereas functions return integers. The last line
defines a Verilog system function $load_mm
that is mapped
to the C function load_mm
from libvbs.so
.
The following shows how the two C functions load_mm
and
display_insn
might be written so as to be able to link
them with the Verilog simulator:
A C function that is mapped to a Verilog task takes no arguments. Arguments are read using the functions#include <stdio.h> #include "tf.h" #ifdef __cplusplus extern "C" { #endif unsigned long load_mm (void); void display_insn (void); #ifdef __cplusplus } #endif unsigned long load_mm (void) { unsigned long address; if (tf_nump() != 1) { printf("Error -- Incorrect number of parameters\n"); return 0xdeadbeef; } address = tf_getnump(1); return mem[address]; } void display_insn (void) { unsigned long data; if (tf_nump() != 1) { printf("Error -- Incorrect number of parameters\n"); return; } data = tf_getnump(1); printf ("%s", mips_dis_insn (data)); return; }
tf_getnump(int)
and tf_getp(int)
. The first
one interprets its argument as an integer, and the second function
returns a string. The function tf_nump(void)
returns the
number of parameters that were passed to the function by the Verilog
code.
To make the C code dynamically linkable, the compiler and linker have
to be passed special flags. We will provide you with a
Makefile
that has the appropriate flags to create shared
object libraries.
$
are not
supported in the CDA 4150 Verilog Simulator. However, this should not affect
your life too much nor restrict the usefulness of the subset of Verilog that
you learn in this course. Many of the more useful built-in commands
are implemented in the CDA 4150 Verilog Simulator, including the
following:
$display
or $monitor
statements.printf
in
C. Differences are that %b
means print the argument in binary,
%h
means print the argument in hexadecimal, and %d
means print the argument in decimal. An example statement might be:
$display("%d: State is now %h, RSaddr is now %h\n", $time, State,
RSaddr);
initial
blocks or always
blocks.$monitor
might be:
$monitor("%d: State is now %h, RSaddr is now %h\n", $time, State,
RSaddr);
$display
example above, but the difference is it will print out any time either
of the signals State
or RSaddr
change their
value. $monitor
statements may only be used inside of
initial
blocks.$stop, $scope, $cleartrace, $settrace, $showscopes
, and
$showvars
.
rd RDcontr // Register File and immediate control logic ( .I1 (IR1), .RSaddr (RSaddr), .RTaddr (RTaddr), .Imm (Imm) );
rd
with a name of RDcontr
, and we
are passing it 4 parameters. To the port defined by the rd
definition as I1
, we are connecting the local signal
IR1
, and so on for the other 3 ports. Named ports are
less error-prone and a nice feature extension to vbs.
{}
, for example:assign PCjmp = {PC[31:28],PCjump,2'b0};This code creates a 32-bit signal called
PCjmp
by concatenating the top 4 bits of the
PC
, with a 26-bit signal called PCjump
and
a 2 bits of 0. vbs, however, did not support code like the
following:assign PCoffset = {{14{I2[15]}}, I2[`immediate], 2'b0};This is a nested concatenation that concatenates 14 copies of the 15th bit of
I2
with the immediate field of an instruction, followed
by 2 low-order zeroes. This is effectively sign-extending the
16-bit immediate field in the MIPS instruction set to a full 32-bit
value. We have changed vbs to support repeat concatenations.
always @(posedge CLK) begin #1 somewire <= someinput; endThe semantics of this statement are "at the rising edge of the clock, wait one tick, and then assign somewire the value of someinput". The problem with this semantics is that THIS IS NOT HOW A FLIP-FLOP BEHAVES. A flip-flop sampled it's input at the rising edge of the clock, and then some time later the output gets that sampled value. The difference is the time at which the input gets sampled. What we want to do is this:
always @(posedge CLK) begin somewire <= #1 someinput; endThis has the desired effect: at posedge CLK, someinput is sampled. 1 tick later somewire gets that sampled value. vbs did not support this placement of the #1 timing directive. It does now.
always
block when a signal appearing in
the sensitivity list changed. In particular, vbs did not handle
memory elements in sensitivity lists correctly such as:always @(RAM[RSaddr]) beginWe have fixed these problems.
assign output = (select) ? a : b;These were broken because the evaluation of this statement in the simulator was not triggered if
a
or b
changed. This has been fixed.
always
block.
|