Pattern Generator
Registers
The majority of patterns are concerned with reading and writing to registers to make the DUT do something and consequently more than 90% of your time developing an Origen test application should be concerned with writing code at the register level.
All test transaction drivers such as the ARM Debug driver are expected to support the basic Origen register API. This allows the test engineer to develop code at register level and independently of the underlying test communication protocol which can be easily changed to something else as required.
See the Registers section of the Models guide for details and examples of how to add and manipulate register states within your model logic.
Interaction with the registers should be limited to the model’s controller - no one else should reach into a model to manipulate its register state (this is not prevented but recommended), and the controllers should instead expose interface methods for the outside world to use. Internally such methods will work by manipulating registers in a sequence.
The read!
and write!
methods when called on a register (or bit) will
automatically fire off a request for the register to be written using the following
rules:
- If the model or controller that owns the register implements a
write_register
method then call this with the target register passed in as the argument - Otherwise if the object that owns the register implements an
owner
method, then see if the object that this returns provides a suitablewrite_register
method - If not then if a currently instantiated model includes the
Origen::TopLevel
module then see if it (or its controller) has awrite_register
method available - If the request has still not been fulfilled then raise an error
Read works in the same way except that it looks for a method called read_register
.
What this all means is that within a controller you will build your test logic from methods that look like this:
def measure_vref(setting=nil)
if setting
ss "Measure the Vref voltage for setting #{setting}"
vref.level.write(setting)
else
ss "Measure the default Vref voltage"
end
vref.test_enable.write!(1)
end
# From a pattern call like this:
Pattern.create do
$dut.nvm.measure_vref(5)
end
The actual mechanism for how the registers are written is abstracted away from the test logic itself and therefore the test logic is generally independent from the communication protocol. This is a very powerful concept that allows plugins to be created that provide test sequences for a specific silicon module and these can then be re-used on DUTs that employ completely different register access protocols.
This is the recommended architecture for modern Origen applications that will lend itself to working well within a plugin-based environment.
Define registers in the child models that own them and create test methods to manipulate them in the controller:
class NVM
include Origen::Model
def initialize
reg :vref, 0x0003, size: 16 do |reg|
reg.bit 15, :test_enable
reg.bit 7..0, :level
end
end
end
class NVMController
include Origen::Controller
def measure_vref(setting=nil)
if setting
ss "Measure the Vref voltage for setting #{setting}"
vref.level.write(setting)
else
ss "Measure the default Vref voltage"
end
vref.test_enable.write!(1)
end
end
Defer how to actually write the register to the top-level SoC controller and normally this would be done via one of the available protocol plugins. Here for example is an SoC which will write the register via the Nexus protocol:
class MySoC
include Origen::TopLevel # Indicate to Origen that this model represents a top-level device object
end
class MySoCController
include Origen::Controller
include Nexus
# Process register reads using the Nexus protocol
def read_register(reg, options={})
nexus.read_register(reg, options)
end
# As above for write requests
def write_register(reg, options={})
nexus.write_register(reg, options)
end
end
And that is all that is required, Origen takes care of the hook up and the behind the scenes communication to make it all work.
Another target may then instantiate a different SoC model which could use a completely different protocol like ARM Debug, in which case the NVM test module would still work although the generated pattern would look completely different.
All registers support bit level updates, we have seen an example of this already:
vref.test_enable.write!(1)
What this will do is update the value held by the given bits and then send the parent
register object to the write_register
method for processing.
All other bits in the register will maintain the state that they had prior to this
operation commencing.
Since it is not generally possible to update only a subset of bits on a device the entire register will still be updated on silicon. However in the case of performing a bit-level read things get a bit more interesting.
The equivalent read operation will update the data values of the bits in the same way, but it will also set a flag on those bits marking that they have been requested for read.
vref.test_enable.read!(1)
The protocol driver can then look out for this flag when generating the readout vectors and only enable a compare on the vectors that correspond to the bits marked for read. Generally this takes a lot of the cognitive overhead out of writing patterns since you can mentally disregard the state of all bits except the ones that you care about.
All standard Origen protocol plugins are expected to support this feature.
The same is true for store (meaning capture the value on the tester) or overlay operations:
# Capture the value of the level bits
vref.level.store!
# Dynamically overlay the value written to the level bits
vref.level.overlay("vref_setting")
vref.write!
Multiple individual bits within a register can be accessed/manipulated within a single
transaction by making multiple bit-level calls to read
(or write
), followed by a single
register-level read!
(or write!
) operation:
my_reg.my_bits_x.read(1)
my_reg.my_bits_z.read(0b101)
my_reg.read!
Sometimes you will see application code that combines the final transaction request with the final bit-level operation, this will do the same thing as the above example:
my_reg.my_bits_x.read(1)
my_reg.my_bits_z.read!(0b101)
If you prefer, an equivalent block form API is also available, this is equivalent to the above example:
my_reg.read! do |reg|
reg.my_bits_x.read(1)
reg.my_bits_z.read(0b101)
end
Generally the code for an existing driver should be reviewed to see how to go about this, the JTAG driver would be a good example to look at, but here are some basic pointers on good driver design.
All drivers should implement read and write register methods:
def write_register(reg, options={})
end
def read_register(reg, options={})
end
The write methods are usually fairly simple, here is a basic example of how to do a parallel and a serial protocol write:
# Writing on a parallel port, let's say we have a 16-bit register and the
# data needs to be written on a pin group called data which is 8-bits
def write_register(reg, options={})
pin(:din).drive(1) # Let's say when this pin is high data is captured
# Drive the data in MSB -> LSB order
pins(:data).drive!(reg[15..8].data)
pins(:data).drive!(reg[7..0].data)
pin(:din).drive(0) # Turn off capture
end
# Writing on a serial port, same as above only this time the :data port is
# only 1-bit wide
def read_register(reg, options={})
pin(:din).drive(1) # Let's say when this pin is high data is captured
# Drive the data in MSB -> LSB order
reg.shift_out_left do |bit|
pin(:data).drive!(bit.data)
end
pin(:din).drive(0) # Turn off capture
end
Read methods are usually a bit more involved to implement the bit-specific read and capture operations. Here is a parallel and serial protocol example which supports bit-level read operations:
# Reading on a parallel port, let's say we have a 16-bit register and the
# data needs to be read on a pin group called data which is 8-bits
def write_register(reg, options={})
pin(:dout).drive(1) # Let's say when this pin is high data is presented
# Compare the data in MSB
8.times do |i|
bit = reg.bit(i + 8)
if bit.is_to_be_read?
pins(:data)[i].assert(bit.data)
else
pins(:data)[i].dont_care
end
end
$tester.cycle
# Now the data in LSB
8.times do |i|
bit = reg.bit(i)
if bit.is_to_be_read?
pins(:data)[i].assert(bit.data)
else
pins(:data)[i].dont_care
end
end
$tester.cycle
pin(:dout).drive(0) # Turn off read out
end
# Reading on a serial port, same as above only this time the :data port is
# only 1-bit wide
def read_register(reg, options={})
pin(:dout).drive(1) # Let's say when this pin is high data is presented
# Drive the data in MSB -> LSB order
reg.shift_out_left do |bit|
if bit.is_to_be_read?
pin(:data).assert!(bit.data)
else
pin(:data).dont_care!
end
end
pin(:dout).drive(0) # Turn off read out
end