Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Memories
Both asynchronous and synchronous memories, which are common in FPGAs, are
supported by Abstract Verilog. These memories have one read port and one write port.
(Single ported memories can be constructed from these dual-ported memories. Memories
with multiple ports are beyond the scope of Abstract Verilog.)
Memories are extensions of registers and are treated similarly. Assignments (writes) to
memories take place at the next clock edge just like with registers. Reads, however, can
be either asynchronous or synchronous. In asynchronous (combinational) memories, the
memory output value changes whenever the read address changes. In synchronous
(pipelined) memories, the memory output value does not change until the next clock
edge.
Memories are declared similarly to Verilog (multiple dimensions from Verilog 2001
are supported):
MEMORY [3:0] y [0:7];
SYNCMEM [7:0] mem [0:127];
In both synchronous and asynchronous memories, writes are just like assignments to
registered signals:
y[i] <-- foo;
mem[0] <-- bar;
That is, the value is written to the memory location on the next rising clock edge. If
two writes are performed to the same location or different locations before the next clock
edge, then only the last write is performed. For example, in
y[2] <-- foo;
y[4] <-- bar;
the first assignment does not execute. Only the second write is executed since the
memory has only one write port. (This should be considered illegal, but the compiler
cannot figure this out.)
Memory reads are done using special variables that are defined by the memory. Reads
to a memory named foo are done into the special read variable named foo_data. This
special read variable can be assigned only by a memory read. Reading asynchronous
memory must use the combinational assignment:
y_data = y[i];
Only one read can be performed in a single clock cycle. (If you perform two reads in a
clock cycle, the simulator may try to do something, but this will certainly not work in the
synthesized circuit. Again, the compiler cannot detect this reliably.)
Reads of synchronous memory must use the sequential assignment operator:
y_data <-- y[k];
This assignment takes place on the next clock tick, which means the value read from
is available in the variable y_data after the next clock tick. Again, the y_data
register is generated automatically when y is declared as a synchronous memory.
Typically, a state machine performs the read in one state and then uses the data in the next
y[k]
state. Of course, reads can be pipelined, so that while the read data is being used, the
next value is being read from memory. (If two reads take place to the same memory
before the next clock edge, only the last read is performed.)
SIZE = 32;
// Async memory
// Must use special variable
Verilog Constructs
You can use many of the usual Verilog constructs when writing Abstract Verilog
programs. For example, Abstract Verilog supports the Verilog 2001 signed declaration:
input signed[23:0] a, b, c;
COMB signed [23:0] temp;
Other useful Verilog features that are supported include localparam, integer.
Some of the more useful features of Verilog are tasks and functions, which allow
abstraction, modularity and reuse in a program.
Functions
Verilog functions correspond to normal (inline) functions in programming languages.
They take a number of inputs, and have a single output. You can think of a function as
representing a combinational logic block with a single output. You may declare COMB
signals in a function, but no REGISTER signals. Here is an example of the max function;
input [7:0] in1, in2, in3;
output [7:0] out;
REGISTER [7:0] out;
COMB [7:0] x, y, z;
COMB [7:0] next;
function max;
input [7:0] a, b, c;
begin
max = a;
if (b > a && b > c) max = b;
if (c > a && c > b) max = c;
end
endfunction
ALWAYS begin
out <-- max(in1, in2, in3);
next = max(x, y, z);
end
Tasks
Do not be confused by the name: Tasks are just generalized functions that can return
multiple values via output ports. (These are similar to Fortran Procedures.) You can
think of a task as a simple module that is connected into your program. As in functions,
you may declare COMB signals, but no REGISTERs. Moreover, Abstract Verilog
currently restricts the outputs of a task to be COMB signals. These outputs can of course
be then assigned to REGISTER signals. Here is an example of a task:
Extras
Abstract Verilog also contains a few extras. For example, the clogb2 function is
declared and can be used by the designer. clogb2 returns the log2 of its argument. This is
useful, for example,
(* *) style comments can appear in memory declarations. This is used to force the
synthesis tools to use Block RAMs for these memories as shown in the following
example:
SYNCMEM signed [31:0] H[0:QUERY_LENGTH-1]
(* synthesis syn_ramstyle="select_ram" *);
Implementation
COMB [3:0] c;
reg [3:0] c;
ALWAYS begin
Implementation
reg [3:0] r, next_r;
always @(posedge clk) begin
if (reset) begin
r <= init;
end else begin
r <= next_r;
end
end
always @(*) begin
next_r = r;
c = bX;
next_r = foo;
r <-- foo;
c = bar;
c = bar;
end
end
Construct
MEMORY [7:0] mem [0:127];
Implementation
reg [7:0] mem[0:127] ;
reg [clogb2(127+1)-1:0] mem_wraddr,
mem_rdaddr;
reg [7:0] mem_wrdata;
wire [7:0] mem_data;
reg mem_we;
always @(posedge clk) begin
if (mem_we) begin
mem[mem_wraddr] <= mem_wrdata;
end
end
assign mem_data = mem[mem_rdaddr];
reg [7:0] bar;
always @(*) begin
ALWAYS begin
mem_data = mem[i];
mem[j] <-- bar;
end
ALWAYS begin
end
begin
begin
mem_rdaddr = i;
end
begin
mem_we = 1;
mem_wraddr = j;
mem_wrdata = bar;
end
end
end