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.
"INPUT", and connect the I/O input pin to
data_outto the receiving logic.
"OUTPUT", and connect the sending logic to
data_inand the I/O output pin to
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_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.
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
data_in, 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 reg [WORD_WIDTH-1:0] data_out, input wire [WORD_WIDTH-1:0] debug_in, input wire debug_in_enable, output reg [WORD_WIDTH-1:0] debug_out ); initial begin data_out = RESET_VALUE; debug_out = RESET_VALUE; end
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,
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
data_out <= (clear == 1'b1) ? RESET_VALUE : data_out;) would
override any previous assignment with the current value of
clear is not asserted!
reg [WORD_WIDTH-1:0] data_in_internal = RESET_VALUE; 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
debug_in_enable is set, then the
debug_in input will show up
debug_out 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
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 always @(*) begin data_in_internal = data_in; debug_in_internal = debug_in; data_out = (debug_in_enable == 1'b1) ? debug_in_captured : data_reg; debug_out = data_out; end end // verilator lint_off WIDTH if (DIRECTION == "OUTPUT") begin // verilator lint_on WIDTH always @(*) begin data_in_internal = (debug_in_enable == 1'b1) ? debug_in : data_in; debug_in_internal = data_in_internal; data_out = data_reg; debug_out = debug_in_captured; end end endgenerate endmodule
Back to FPGA Design Elements