An Input/Output Register with extra attributes to make the CAD tool place it in special I/O register locations around the edge of the FPGA, thus reducing skew. Also has extra debug inputs and outputs for on-chip test logic to avoid disturbing the placement of the I/O register.
DIRECTION
to "INPUT"
, and connect the I/O input
pin to data_in
, and data_out
to the receiving logic.DIRECTION
to "OUTPUT"
, and connect the sending
logic to data_in
and the I/O output pin to data_out
.It's likely for the I/O Register to be inside another module and thus
invisible. Connecting any logic at the same time as the I/O pin to
data_in
or data_out
will prevent placing the I/O Register into an I/O
location since we can only connect one thing at a time to an I/O pin. So we
provide separate debug signals which do not touch the I/O pin.
The debug_out
port mirrors data_out
and is usually brought out to the
ports of the module enclosing the I/O Register, so you don't have to alter
the module logic to monitor operation. While a given debug_in_enable
bit
is set, then the corresponding debug_in
replaces the same data_in
bit,
which allows for injecting test signals without using extra pins.
`default_nettype none module Register_IO_Single_Ended #( parameter WORD_WIDTH = 0, parameter [WORD_WIDTH-1:0] RESET_VALUE = 0, parameter DIRECTION = "" // "INPUT" or "OUTPUT" ) ( input wire clock, input wire clock_enable, input wire clear, input wire [WORD_WIDTH-1:0] data_in, output wire [WORD_WIDTH-1:0] data_out, input wire [WORD_WIDTH-1:0] debug_in, input wire [WORD_WIDTH-1:0] debug_in_enable, output wire [WORD_WIDTH-1:0] debug_out );
This module is a specialization of the simple Synchronous Register module, since we must apply the attributes directly to the HDL register and not the module as a whole. See the Synchronous Register module for further documentation and discussion.
Depending on your CAD tool, optimization passes may or may not remove and reconstruct the register and thus lose the attributes specifying placement of the register into an I/O buffer location. Also, applying other attributes may interfere with I/O buffer placement.
Under Vivado, using a DONT_TOUCH
attribute or constraint on this module,
or the data_reg
register, will prevent the IOB
attribute from taking
effect, as it inhibits any optimization, including placement into an IOB
location. However, we must use a KEEP
attribute to prevent optimization
transformations on this register before placement, as mentioned above.
// Quartus (* useioff = 1 *) // Vivado (* KEEP = "TRUE" *) (* IOB = "TRUE" *) reg [WORD_WIDTH-1:0] data_reg = RESET_VALUE;
Here, we use the "last assignment wins" idiom (See
Resets) to implement reset. This is also one
place where we cannot use ternary operators, else the last assignment for
clear (e.g.: data_out <= (clear == 1'b1) ? RESET_VALUE : data_out;
) would
override any previous assignment with the current value of data_out
if
clear
is not asserted!
wire [WORD_WIDTH-1:0] data_in_internal; always @(posedge clock) begin if (clock_enable == 1'b1) begin data_reg <= data_in_internal; end if (clear == 1'b1) begin data_reg <= RESET_VALUE; end end
We also mimic the I/O Register with a conventional register taking a debug
input. If a debug_in_enable
bit is set, then the matching debug_in
input bit will show up at the same data_out
bit and debug_out
bit
outputs instead of data_in
, with the same 1-cycle latency. This enables
generating signals and testing without having to use extra FPGA pins or
disturbing the I/O register placement.
reg [WORD_WIDTH-1:0] debug_in_internal = RESET_VALUE; wire [WORD_WIDTH-1:0] debug_in_captured; Register #( .WORD_WIDTH (WORD_WIDTH), .RESET_VALUE (RESET_VALUE) ) debug_reg ( .clock (clock), .clock_enable (clock_enable), .clear (clear), .data_in (debug_in_internal), .data_out (debug_in_captured) );
We wire up the debug register and logic as necessary for INPUT
or
OUTPUT
operation. The debug logic must never touch the I/O pin.
generate // verilator lint_off WIDTH if (DIRECTION == "INPUT") begin // verilator lint_on WIDTH Multiplexer_Bitwise_2to1 #( .WORD_WIDTH (WORD_WIDTH) ) debug_bit_select ( .bitmask (debug_in_enable), .word_in_0 (data_reg), .word_in_1 (debug_in_captured), .word_out (data_out) ); always @(*) begin debug_in_internal = debug_in; end assign data_in_internal = data_in; assign debug_out = data_out; end // verilator lint_off WIDTH if (DIRECTION == "OUTPUT") begin // verilator lint_on WIDTH Multiplexer_Bitwise_2to1 #( .WORD_WIDTH (WORD_WIDTH) ) debug_bit_select ( .bitmask (debug_in_enable), .word_in_0 (data_in), .word_in_1 (debug_in), .word_out (data_in_internal) ); always @(*) begin debug_in_internal = data_in_internal; end assign data_out = data_reg; assign debug_out = debug_in_captured; end endgenerate endmodule