Often it is desired to instantiate device primitives in a design.
This post is going to review, with an example, how device primitives
can be instantiated using user defined code.
In this example we will use the basic IO SERDES available in
some devices. This SERDES simply takes a parallel input and
generates a serial output. I call this simple to differentiate
between the more complex high speed transceiver available in
some high-end FPGAs.
My preference is to attempt and create vendor agnostic blocks
that will use the vendor specific templates, the following
conditionally instantiates the correct vendor block.
@myhdl.block
def simple_output_serdes(clock_par, clock_ser, reset, datain, serialout,
vendor=None):
"""Simple output serializer
Arguments:
clock_par: The clock for the parallel data.
clock_ser: The clock for the serial data, clock_ser should be N times
greater than clock_par, where N is the number of bits being serialized
reset: system active high reset
datain: parallel data in
serialout: serial data out
"""
assert vendor is None or vendor in ('A', 'L', 'X')
vendor = 'my' if vendor is None else vendor
# all the primitives have the same ports
portmap = dict(
clock_par=clock_par, clock_ser=clock_ser, reset=reset,
datain=datain, serialout=serialout
)
# create a mapping to the different blocks
blockmap = dict(
my=my_output_serdes,
A=alt_output_serdes,
L=lat_output_serdes,
X=xil_output_serdes,
)
prim_inst = blockmap[vendor](**portmap)
return prim_inst
In the above the my_output_serdes
is a convertible / synthesizable
version the serializer, the others will use the user defined
code to instantiate the device primitive.
x_prim_count = 0
@myhdl.block
def xil_output_serdes(clock_par, clock_ser, reset, datain, serialout):
global x_prim_count
serialout.driven = True
datain.read = True
inst_count = x_prim_count
# do some checking, the primitive has limited modes
assert len(datain) in range(1, 5)
data_width = len(datain)
# use the behavioral model for simulation this will be
# overwritten during conversion
prim_inst = my_output_serdes(clock_par, clock_ser, reset,
datain, serialout)
x_prim_count += 1
return prim_inst
xil_output_serdes.verilog_code = """
wire [7:0] data = $datain;
wire clock_ser_n = ~$clock_ser;
OSERDES2_$inst_count
#(
.BYPASS ( "FALSE"),
.DATA_RATE_OQ ( "SDR" ),
.DATA_RATE_OT ( "SDR" ),
.DATA_WIDTH ( $data_width ),
.OUTPUT_MODE ( "SINGLE_ENDED" )
)
xil_oserdes_inst_$inst_count
(
.CLKDIV ( $clock_par ),
.CLK0 ( $clock_ser ),
.CLK1 ( clock_ser_n ),
.RST ( $reset ),
.IOCE ( 1'b1 ),
.D1 ( data[0] ),
.D2 ( data[1] ),
.D3 ( data[2] ),
.D4 ( data[3] ),
.OQ ( $serialout )
);
"""
The above is just and example, in an actual implementation
there might be more parameters that are passed to the primitive
or the primitives are cascaded, etc.