Three real device models are included in the Simics Base package; an AM79C960
(ISA) Ethernet controller, a DS12887 real-time clock and a DEC21140A (PCI)
Ethernet controller. They contain full source code and some tests. You can use
these models and tests as examples when developing your own device models. This
chapter serves as an orientation about how those devices are modeled and shows
how you can test them if you have access to the Enterprise machine or
Firststeps for the DEC21140A.
Source packages also contain devices which can be used as examples. If you develop a different kind of model than the ones in Model Builder, you may find a device in one of the source packages a better example.
AM79C960 was a rather common ISA Ethernet card, used mostly in PCs. It is a little dated now, but still serves as a good example of how to implement Ethernet.
The workable source code for the sample device can also be found in the
directory [simics]/src/devices/AM79C960. If you want to try modifying the
AM79C960 module yourself, we recommend that you set up a user project and copy
the source code there, as described in section
3.4.
Please do read previous sections in part II to know how to compose and build an empty device, and we will ignore those general parts in this section and highlight the methodologies for how to efficiently model a specific device.
Typical work flow for Simics modeling includes below four stages:
As the first stage, modeling materials preparation should be done before the modeling of the device, the necessary materials include hardware spec, target OS driver, and test applications, and optional software manuals. Hardware spec is the most important document, it also serves as the root spec of the model and the bridge between modeling team and the software team. Do make sure you get the right version of them.
We will elaborate the other three stages by tearing down the modeling methodology of an AM79C960 device.
There is always a system block diagram in the hardware spec, which perfectly shows the system level functional blocks, AM79C960 does as well, it has even two: one is bus master mode and the other is shared memory mode, there are some difference on ISA bus interface unit, since Simics will not simulate the details of a bus arbitration, we could regard them as one in the modeling.
From the diagram we know that AM79C960 has the major functional blocks like ISA/EISA bus, IEEE 802.3 Ethernet port, transmit and receive FIFOs, etc. It receives frames from ISA bus and sends them to Ethernet port, and vice versa. Our model should focus on those data handling, implement the actions the software calls for the device.
As Simics is a functional simulator, we need not model details how the device works internally, for example, the Ethernet PHY block actually has line encoder/decoder (MENDEC), frame preamble detector, etc. sub functional modules, yet they are not visible to the software, so we will hide those details and handle the data octets directly.
Simics itself is built up by several fundamental modules, we also suggest the user to divide the complex device module into several sub modules and develop them separately. In each simple module, user always can utilize provided well-defined Simics libraries and templates to facilitate the design, and through interfaces, each module can be connected with each other efficiently.
It is good practice to try to reuse existing modules and build a modular system.
The AM79C960 module simplified the design and implemented all functionalities
in one module because it is simple enough. If you are modeling a more complex
Ethernet controller, it is better to at least divide it into PHY and MAC
modules. You could then reuse the generic_eth_phy module and get helpful
references from the implementation of the ich10_lan_v2 module, avoiding
duplication of common PHY and MAC functionalities and allowing you to improve
and debug them separately.
Once the sub modules are defined, we should then begin the interfaces design. In Simics, interface is the primary way to transfer data between modules, see section 5.5 for more details.
According to the device block diagram, there are several interfaces, meaning connections to outside of the module, that should be included in this model:
The outgoing Ethernet link could be implemented as:
connect link {
param documentation = "The ethernet-link the device is connected to.";
interface ethernet_common;
}
The incoming Ethernet link could be implemented as:
implement ethernet_common {
method frame(const frags_t *frame, eth_frame_crc_status_t crc_status) {
receive_packet(frame);
}
}
When another device is trying to send data to this device, the interface method
frame is called, the user is expected to handle the incoming data within the
method.
Interrupt is a very common interface for the device to inform some internal
events, ex. one frame is received or transmitted, error condition is detected,
etc., we can use simple_interrupt interface to do that:
connect irq_dev {
param documentation = "The device that interrupts are sent to.";
param configuration = "required";
interface simple_interrupt;
}
The
simple_interruptinterface is deprecated, user should usesignalinterface in the new design.
To record current level of interrupt line and avoid to raise or lower the
interrupt line twice, we need an attribute somewhat like irq_raised. This is
also one suggested design practice in Simics to support checkpointing, when
Simics restores the running from a checkpoint, the current runtime interrupt
status should be correctly restored as well, this is supported by utilizing an
attribute, whose value will be automatically saved and restored when the
checkpoint is written and read.
To support DMA and share memory with the target CPU, we use map_target
template that allows the DML connect object to connect to any memory as a bus
master. Through this connect, the device is able to access the initialization
data and Ethernet descriptor rings which are mapped on the system bus:
connect memory is (map_target) {
param documentation = "The memory the device uses to access the "
+ "initialization data and descriptor rings";
param configuration = required;
}
AM79C960 supports both shared memory operations mode and bus master mode. In the first mode, AM79C960 is visible as one shared memory space to the master CPU, all registers are mapped on the memory space. In the second mode, AM79C960 has the ability to perform DMA operation, access data from system bus directly. In Simics, there needs no special design for DMA controller, the device can use interface call to access the memory space at any time.
ISA bus logically is seen as an range of linear address memory space from the
viewpoint of software, but there is no need to implement bus details, for
example, bus arbitration, IOR/IOW signals, etc., instead, we implement this bus
functionality as a register bank. DML bank implicitly implements the
io_memory interface to make it accessible from the CPU, so the user needs only
declare the registers offset, name, size etc. parameters, and focus on its
registers logic design, which in many cases is the most important and
time-consuming part in a device modeling.
Don’t forget to check the interfaces configuration, some of them are optional
and may be left unconnected during initialization or even all the runtime. So,
in case the interface is optional, user needs to check the connection before
calling its methods, just like what AM79C960 module does in send_packet()
method:
method send_packet() {
local physical_address_t txd_addr;
// ... other preparation statements
if (link.obj != NULL) {
// ... prepare frame buffer
link.ethernet_common.frame(&frame, Eth_CRC_Match);
} else {
log info, 2: "not connected, not packet sent";
}
// ... post handling of sending
}
Most device logic is triggered by register accesses. This logic can be designed into the registers’ side effects or into global functions. It is good style to design (declare) the register bank in one place and implement the behavior in another, as it makes the implementation clearer.
Let us take mac_address as an example. It is a primary attribute for an
Ethernet controller. The MAC address is located at I/O registers address 0 ~ 5,
which are set by the CPU. By utilizing the attr_accessor template, any write
to registers aprom_0 through aprom_5 also updates the mac_address
attribute, which is then used to check the destination address when a frame is
received.
Another example is the package transmitting, which is triggered by CSR0 writing. This occurs when the driver prepared the data to be transmitted, configured the descriptor ring accordingly, and then writes the CSR0 to start transmitting. To implement this behavior, we add side effect to the CSR0 writing:
register csr0 {
// will csr0.init() after csr0 written?
session uint1 do_init;
// will csr0.start() after csr0 written?
session uint1 do_start;
// will csr0.transmit_demand() after csr0 written?
session uint1 do_transmit_demand;
method write_register(uint64 value, uint64 enabled_bytes, void *aux) {
default(value, enabled_bytes, aux);
if (do_init == 1) {
do_init = 0;
initialize();
}
if (do_start == 1) {
do_start = 0;
start();
}
if (do_transmit_demand == 1) {
do_transmit_demand = 0;
transmit_demand();
}
}
field TDMD @ [3] "Transmit Demand" {
is write;
method write(uint64 value) {
if (value == 1) {
this.val = 1;
do_transmit_demand = 1;
}
// ignore write 0; cleared by send_packet()
}
}
We update the demand bit when writing, as well as other bits in case more than one bit is written, then check the demand bit after writing. It is better to implement the packet sending outside of the register definition, as it could be quite complex.
From related register bits definition and explanation of descriptor ring buffer
management, we can conclude below procedure when transmitting one packet frame
(Refer to send_packet() method for the implementation):
Let’s also take a look at receiving a packet, this occurs when external device
call the ethernet_common interface method (Refer to receive_packet() method
for the implementation):
The descriptor ring used by transmitting and receiving block is a very common data structure for the Ethernet controller, let’s take a look at the transmit descriptor ring implementation in this device as an example.
typedef struct {
physical_address_t addr;
uint1 OWN;
uint1 STP;
uint1 ENP;
uint16 size;
} txd_t;
method txd_from_buf(uint8* buf) -> (txd_t) {
local txd_t txd;
txd.addr = buf[0] | buf[1] << 8 | buf[2] << 16;
txd.OWN = buf[3][7];
txd.STP = buf[3][1];
txd.ENP = buf[3][0];
txd.size = - (buf[4] | buf[5] << 8);
return txd;
}
method txd_to_buf(txd_t txd, uint8* buf) {
local int neg_size = - txd.size;
buf[0] = txd.addr[7:0];
buf[1] = txd.addr[15:8];
buf[2] = txd.addr[23:16];
buf[3] = (txd.OWN << 7) | (txd.STP << 1) | (txd.ENP << 0);
buf[4] = neg_size[7:0];
buf[5] = neg_size[15:8];
buf[6] = 0;
buf[7] = 0;
}
The descriptor ring buffer is built up by numbers of descriptors, each
descriptor entry has the same 8 bytes structure, when reading out one transmit
descriptor, AM79C960 module extracts its inner data and assigns them to a
txd_t type value, for later on convenient access. At writing back, the value
is mapped back to the buffer data structure accordingly.
Indexing of the descriptor ring buffer is somewhat tricky,
it originally locates at the transmit ring counter register at CSR74, yet this
device uses an attribute curr_txd to simulate its behavior,
it works well from the software point of view and is quite simple to be
implemented:
attribute curr_txd is uint64_attr {
param documentation = "Index of the current transmit descriptor";
param configuration = "optional";
method addr() -> (physical_address_t) {
return xmt_descr_tbl_addr.val + (this.val * TXD_SIZE);
}
method next() {
this.val = (this.val + 1) % xmt_descr_tbl_length.val;
}
}
The AM79C960 model is far from complete, it implements just enough
functionality that the device can be used with Linux 2.4. There always are too
many registers to be implemented, in practice, we only implement the smallest
set of registers required for the software to work correctly, and leave those
not necessary as unimplemented (could use unimpl type logging to record). This
completeness could be verified by target software, so when the software is
upgraded, the model probably will have to be updated as well.
Attributes are also widely used in Simics to record internal state or be exposed
to user for configuration and debugging, for example, the 64-bit Logical Address Filter that resides in CSR8 - CSR11 is stored in the
logical_address_filter attribute. Some of the registers, for example, CSR24
and CSR25 that contain Base Address of Receive Ring, are not even implemented
and the values are only stored in separate attributes.
Another example is poll_interval, it originally locates in CSR47, but we could
use an attribute to simplify the implementation, while keep the ability to
configure that at runtime.
In case the device needs some asynchronous handling, such as deferred
transmitting process, Simics provides event to support this behavior:
event poll_txd is simple_time_event {
method event() {
send_packet();
poll_txd.post(poll_interval.val);
}
}
This event periodically polls the transmit descriptor ring buffer, if there are any descriptors previously pending to be transmitted, they’ll be checked again by the event handler. The polling is then rescheduled for the next round.
There is no need to design the event in receiving block, every time the packet is received, it can be handled immediately.
Simics provides template to avoid duplicating the same code, one good example
for this common practice is data_accessor:
template attr_accessor {
param attr;
param attr_msb;
param attr_lsb;
param ignore_write default false;
param mac_register default false;
method get() -> (uint64) {
return read();
}
method set(uint64 value) {
write(value);
}
is read;
method read() -> (uint64) {
return attr.val[attr_msb:attr_lsb];
}
is write;
method write(uint64 value) {
if (!ignore_write) {
attr.val[attr_msb:attr_lsb] = value;
}
}
}
This template implements register default get/set and read/write behaviors, and
the user could customize this kind of register by setting parameters
accordingly. Remember that, the user can always find good templates from the
utility.dml library and define their own templates as they like.
Any device needs some methods to be reset to known states. By instantiating
the sreset utility template, our Simics device will be provided with
soft_reset semantics, this reset will reset all banks and registers
recursively by default, unless those explicitly ignored:
// should be read/write accessible only when STOP is set
register csr1 is sticky;
// should be read/write accessible only when STOP is set
register csr2 is sticky;
These use the utility template sticky which overrides the default soft_reset behavior, and prevents them from being reset to the default value during soft reset.
In case some other elements need to be initialized, such as attributes, user can
place them in init() or post_init() methods, see section
4.2.7.3 for more details.
Functional tests and integration tests are both heavily used in Simics device
modeling, normally functional tests are located in the test subdirectory in
the device module, user could execute make test to perform them at any time.
Integration tests are much more complex, user needs to connect devices and
components in the Simics configuration and run the target OS, generally use
target application to test the device models.
AM79C960 does not provide functional tests, so we will not explain that here.
Yet if your distribution contains the simulated machine enterprise, you can
find the Simics script enterprise-common.simics in the directory
[simics]/targets/440bx. Let’s see how to utilize this script to verify the
functionalities of this model. This file creates an enterprise machine using the
AM79C960 module. The AM79C960 object is called
enterprise.motherboard.lance.lance.
To do something interesting with the AM79C960 model it needs to be connected to
something that it can talk to. The default setup for the enterprise system has
the AM79C960 controller connected to a service-node via an Ethernet switch.
Start the simulation and let the machine boot and login as the user root. No
password is required. Stop the simulation and set the log level of the lance
object to 2:
comment: <> (Following target is no longer distributed, so we cannot test it in t123) comment: <> (run-script “targets/440bx/enterprise-common.simics”) comment: <> (console enterprise.console.con) comment: <> (run-seconds 45) comment: <> (input root\n) comment: <> (run-seconds 0.1)
simics> enterprise.motherboard.lance.lance.log-level 2
[enterprise.motherboard.lance.lance] Changing log level: 1 -> 2
You can now start the simulation again and send a ping packet to the
service-node by entering ping -c 1 10.10.0.1 in the console on the simulated
machine. The lance object will log what happens:
[enterprise.motherboard.lance.lance info] Packet sent, dst 20:20:20:20:20:00, src 10:10:10:10:10:30, length 102 bytes
[enterprise.motherboard.lance.lance info] packet received, dst 10:10:10:10:10:30, src 20:20:20:20:20:00, length 102 bytes
[enterprise.motherboard.lance.lance info] MAC address matches, packet accepted
The enterprise machines sends a 102-byte packet to the service-node and
receives a 102-byte reply. These are the actual ping request and ping reply.
If you want more detailed logs you can change the log level to 3 or 4. At log level 3 a lot more information about what is going on in the device will be logged. The device polls for packets to transmit regularly, so this will cause a lot of output. At log level 4 all accesses that the processor does to the device will also be logged.
By the test applications we make sure the basic transmitting and receiving work
well and the driver can access the device as expected. As a good practice, we
should document those key unimplemented features as limitations at the top of
the model, just like AM79C960 module does:
param limitations = ("<ul>"
+ "..."
+ "<li>The ISA bus configuration registers are not implemented</li>"
+ "..."
+ "</ul>");
In case new features are required, we could define the new test scenarios and develop that add-on features based on this ready model, continuously improve the model’s completeness.
DS12887 is a very common real-time clock device. It is used, among other places, in common PCs. There are also many other devices that are extensions of the DS12887, for example, DS17485 and M5823.
It may be good to have the documentation for the DS12887 chip when looking at
the sample code, so that you can compare the code to the specification. The
documentation can be found on the Internet, search for ds12887.pdf on
www.google.com and you will find several links to it.
The source code for the sample device can be found in the directory
[simics]/src/devices/DS12887. If you want to try modifying the DS12887
yourself, we recommend that you set up a user project and copy the source code
there, as described in section
3.4.
The source code of the DS12887 module is quite richly commented, so if you have the documentation for the DS12887 chip you should hopefully be able to understand most of the code without too much problem.
Refer to the address map of DS12887 from the spec, the major function blocks of
the DS12887 block diagram is clearly the calendar and alarm ram (block “CLOCK,
CALENDAR, AND ALARM RAM”) and the control registers (block “REGISTERS A,B,C,D”).
The output signals of this model are also simple, the main output signal is the
interrupt (signal “IRQ”). We can abstract DS12887 with a real time clock device,
it increases its internal time counter per second. The date and time are stored
in registers. Alarm interrupts can be controlled by registers if clock match the
values in alarm registers. As Simics is a functional simulator, we do not need
to exactly model such details. Instead we design an attribute base_rtc_time to
store absolute time, so that we only update RTC time when time registers are
really accessed. Also when registers are accessed we compute and schedule the
alarm interrupt rather than compare and decide if the alarm interrupt needs to
be triggered. This is also known as common practice to improve the simulation
performance.
As registers are the main function blocks of DS12887. Firstly we write the layout of the register banks. The address and size of registers can be found in the documentation.
bank registers is function_mapped_bank {
param register_size = 1;
param function = 1;
register seconds @ 0;
...
register nvram[i < 114] @ 14 + i;
}
As we introduced previously, we implement the model to update the time registers
“lazily”. The handling logic looks more complicated than absolutely necessary,
but does help simulation performance. A simple implementation could post an
event that raises the UIP flag and then an event that lowers the UIP flag,
updates the time registers and compares them with the alarm registers each
simulated second. Such simple implementation actually would lead to many regular
Simics events just for idle loop, in case if the time registers and alarm
registers are not really accessed. To avoid having to frequently post these
events, the model instead saves the simulated time that the real-time clock time
was last set, and the time it was set to. From this information the current
real-time clock time can be calculated at any time, and the time registers are
only updated when they are read. We actually develop template time_register to
include such time updating methods as update_time_registers_conditional() and
writeback_time_registers() so time registers can adopt such functionalities.
There is a comment above the base_time attribute in the source code that
describes in more detail the time representation.
Similarly, events for the alarm interrupt, periodic interrupt and update-ended
interrupt are only posted if the corresponding interrupt flag is not already
raised. We also develop template interrupt_event to implement such interrupt
scheduling functionalities, mostly by the update_time() method in it. The
interrupt related registers or register fields, for example, the alarm
registers, can adopt the template. This implementation means that, if Linux does
not use the device after the boot, as we generally observed in our simulated
scenarios, the model will have good simulation performance since it does not
need to post any more events once Linux has booted.
DS12887 spec well describes the control registers. The 3 DV bits in register a
enable the oscillator and interrupt posting. The 4 rate-selection bits decide
how to generate the periodic interrupt. Register b include the interrupt
enable bits, and the interrupt flag bits are in register c. To better maintain
such interrupt functionalities we implement common method update_IRQF() to
check, update those fields, and raise or lower the interrupt. This is also a
common practice eventually seen in interrupt processing of every DML devices.
Interrupt related events and register fields can do individual update, then call
the common method to sync the interrupt state, without spreading similar or even
inconsistent code in different DML code pieces.
A difference between the documentation of the DS12887 and the model is that the
model has three register banks, while the documentation only describes one. This
is because of the way the device is used in PCs. The registers described in the
documentation correspond to the registers bank. When the device is used in a
PC a small translation device with two registers that forwards accesses to the
registers of the DS12887 is mapped in the port space. This translation device
corresponds to the port_registers bank. In addition we define a bank
partially_persistent_registers which serves as persistent backing storage for
registers where not all fields are persistent. See the section 7.2 of the second
chapter of the Simics User’s Guide and the DML 1.4 Reference Manual for
details. If you want to use the model as a pure DS12887, just ignore the
port_registers bank.
According to the functions described in the spec, there are several interfaces that should be included in this model:
We can use the interrupt interface simple_interrupt to really trigger the
interrupt from DS12887 internal events:
connect irq_dev {
param documentation = "The device that interrupts are sent to,"
+ " or Nil if the interrupt line is not connected to anything.";
param configuration = "optional";
interface simple_interrupt;
}
The
simple_interruptis obsoleted by thesignalinterface.
We connect to an outside timer to get the current virtual time. We need it to calculate the real time.
local conf_object_t *clock = SIM_object_clock(dev.obj);
if (clock == NULL) {
log error: "device does not have a time queue/clock";
} else {
SIM_require_object(clock);
}
One amazing capacity of Simics environment is checkpointing and snapshotting.
The Simics environment can store or restore DS12887 states to or from a Simics
checkpoint or snapshot. This actually leads to an extra step in Simics modeling
to see if the model is checkpointing safe. For a Simics timer model as DS12887,
reschedule its time events or time interrupts after restore is generally needed.
Aware of that, we also implement the temporal_state interface of such
computing and rescheduling.
We mainly picked alarm interrupts as interrupt example in previous sections. Actually DS12887 also support periodic interrupts and update-ended interrupts. The implementing experience of alarm interrupts can be easily used in other interrupts according to the details described in the spec.
Templates facilitate Simics model code consistency, debugging and device
hierarchy. For example, we also implement template rtc_register to be used by
all time and alarm registers, which contains methods for register value range
checking and converting between BCD format and binary format. DML enables
template parameters so that the template method just implement the value
boundary checking logic and leave the boundary value setting to each individual
registers. We also implement other templates as hour_rtc_register,
time_alarm_register, irq_flag, irq_enable_flag accordingly.
There are some DS12887 hardware functionalities are not needed in Simics
functional modeling. For example, we do not need the details of oscillator but
just need to monitor if it is enabled or not, to trigger the interrupts, from
the DV bits of register a. We do not implement the power switch because this
is generally not needed in a functional simulation. We also do not implement the
daylight saving time and square wave output functionalities since we do not
observe software requiring it. We record such non-implemented functionalities
with limitations parameter for future enhancement.
If your distribution contains the simulated machine enterprise, you can find
the Simics script enterprise-common.simics in the directory
[simics]/targets/440bx. This file creates an enterprise machine using the
DS12887 module. The DS12887 object is called rtc0.
You can, for example, log what is happening to the device during the boot by
setting the log level of the rtc0 object to 3:
simics> rtc0.log-level 3
[rtc0] Changing log level: 1 -> 3
simics> c
[rtc0 info] Update-ended interrupt triggered, raising UF.
Pressing return
[rtc0 info] Periodic interrupt frequency set to 1024.000000 Hz.
[rtc0 info] Periodic interrupt triggered, raising PF.
[rtc0 info] UF lowered.
[rtc0 info] PF lowered.
[rtc0 info] UIE set.
[rtc0 info] Periodic interrupt triggered, raising PF.
[rtc0 info] Update-ended interrupt triggered, raising UF.
[rtc0 info] Raising interrupt.
[rtc0 info] UF lowered.
[rtc0 info] PF lowered.
[rtc0 info] Lowering interrupt.
[rtc0 info] UIE cleared.
[rtc0 info] Periodic interrupt triggered, raising PF.
[rtc0 info] Update-ended interrupt triggered, raising UF.
If you raise the log level to 4 all access the processor does to the device will
be logged. The rtc0 object is accessed a lot during the boot, so you probably
do not want to run the entire boot with log level 4.
Note that Linux only uses the real-time clock while booting and shutting down. Once it has booted it uses other timers to keep the time, so to get Linux to access the D12887 again once it has booted, you have to reboot the system.
The DEC21140A is a PCI Ethernet card. As the AM79C960, it is obsolete today but
it provides a good example of a PCI device written in DML. Its specification can
be found on the Internet, for example by looking for ec-qn7nc-te.ps.gz with
Google.
The source code for the sample device can be found in the directory
[simics]/src/devices/DEC21140A-dml. If you want to modify the DEC21140A-dml
module yourself, we recommend that you set up a user project and copy the source
code there, as described in section
3.4.
Module DEC21140A-dml is a DML-implemented DEC21140A device, the -dml suffix
is to differentiate with the old C-implemented one. The DEC21140A-dml
implementation is more readable and modern by utilizing DML powerful modeling
features, for example, by importing Simics PCI library, the PCI bus
implementation is pretty simple and the latest PCI bus features are well
supported.
Both AM79C960 and DEC21140 are Ethernet controllers, they have many common parts in Ethernet functionalities, for example, descriptors list (or ring), frame transmit and receive handling, etc., the biggest difference is that, AM79C960 uses ISA bus, yet DEC21140A uses PCI bus to connect to the host.
Another point that should be highlighted here is that, module DEC21140A-dml
does not implement the PHY layer functionalities itself, instead, it reuses
generic_eth_phy when building the Simics component, this kind of design
practice is highly recommended in Simics modeling.
With the help of PCI standard library and reusing existing PHY module, there are only few necessary device specific implementations to be done, including device status transaction, MAC layer packets handling, interrupt reporting, etc..
For better readability, the source code has been divided into two files:
DEC21140A.dml This file contains the register bank
definitions for the PCI configuration, device interfaces and the CSR
registers.DEC21140A-eth.dml This file contains code to
handle the network and Ethernet frames, mainly concerning the transmit() and
receive() methods.The two files also indicate the two key parts for this device in modeling, the host connections which includes PCI bus and CSR registers, and the Ethernet connection.
Similar with other Simics modules, the DEC21140A-dml module is far from
complete, it implements just enough functionality that the device model can be
used with Linux.
As described in the specification, the DEC21140A and the host driver communicate through two data structures: control and status registers (CSRs), descriptor lists and data buffers.
CSR registers are implemented in a Simics bank, its base address will be
assigned by BIOS through PCI configuration space BAR register when booting the
system. The descriptors lists locate in the host memory and are composed by
pointers to the host memory, the DEC21140A-dml module reads and writes those
descriptors and the real data through DMA function of the PCI bus.
With the help of Simics PCI library (by importing the file pci/common.dml),
module DEC21140A-dml only needs to fill the necessary fields in the PCI
configuration space to implement the PCI bus, such as vendor_id, device_id,
BARs, etc..
For more info about how to model a PCI device in general, please refer to the Technology Guide “PCIe Modeling Library”
Ethernet connection is implemented by below interfaces:
ieee_802_3_phy_v2, connect to the outside PHY module, for example, a
generic_eth_phy module.ieee_802_3_mac and ieee_802_3_mac_v3, to be connected as
a MAC module.mii_management to allow software to communicate with the PHY.Interface microwire is to connect to a serial EEPROM, where some customizable
data, ex. MAC address, are stored in.
As a good practice, the CSR bank declares the registers and only implements small-logic registers in place. All other logic, such as interrupting and Ethernet frame transferring, is placed elsewhere.
The CSR registers should be designed in the bank which
functionnumber is equal to themap_funcassigned in PCI config bankpci_configBARs, so the CSR bank can be accessed by the software through PCI bus.
Register CSR0 contains the bus mode configuration. Under normal conditions, most of these do not need to be implemented, so they are left as dummy bits. For example, the software reset is just logged without any real action—this is good enough for the device module to work with the target Linux version.
To avoid duplicating the same kind of register behavior, ex. checking device
status before any writes to the register, three templates are designed, named as
rw_tx_stopped, rw_rx_stopped and rw_rx_tx_stopped, this kind of design
pattern is suggested in Simics modeling. For example:
template rw_tx_stopped {
is write;
method write(uint64 value) {
if (csr.csr5.ts.val != TX_STOPPED && value != this.val)
log spec_viol, 3:
"writing 0x%x to the %s.%s.%s field with running Tx process",
value, bank.name, reg.name, this.name;
this.val = value;
}
}
There are two registers that are implemented as unmapped registers,
current_rx_address and current_tx_address, they are updated every time the
CSR3 and CSR4 registers are written separately, and updated accordingly when the
descriptors are processed. By doing like this, those two registers are kept as
internal variables and not able to be accessed from the software directly, yet
they are still automatically checkpointed by Simics and can be accessed by
fields, like an ordinary register does.
Interrupt reporting usually needs some pre-checking for the current level, mask
bits setting, enablement, etc. conditions, so in most cases we implement the
interrupt raise and lower operation in a dedicated method, just like what
DEC21140A-dml does to raise an interrupt bit:
method raise_interrupt(int bit) {
if (csr.csr5.itr.val[bit] == 0)
log info, 4: "raise interrupt %d", bit;
csr.csr5.itr.val[bit] = 1;
update_interrupts();
}
method update_interrupts() {
if ((csr.csr5.itr.val & INT_ABNORMAL_MASK & csr.csr7.itr_mask.val) != 0)
csr.csr5.ais.val = 1 & csr.csr7.ais_mask.val;
else
csr.csr5.ais.val = 0;
if ((csr.csr5.itr.val & INT_NORMAL_MASK & csr.csr7.itr_mask.val) != 0)
csr.csr5.nis.val = 1 & csr.csr7.nis_mask.val;
else
csr.csr5.nis.val = 0;
if ((csr.csr5.nis.val | csr.csr5.ais.val) != 0)
pci_config.pci_raise_interrupt();
else
pci_config.pci_lower_interrupt();
}
In the QSP-x86 package you can find the script qsp-linux-dec21140.simics in
the directory [simics]/targets/qsp-x86. This file creates a QSP-x86 based
machine and attaches the DEC21140A-dml device to its PCI bus and the Ethernet
link. It also detaches the internal network device of the QSP-x86 from the
Ethernet link, so the DEC21140A is the only network device connected to the
link.
Start the simulation and let the machine boot. Now you can see the network device and use it to ping the service node which is connected to the Ethernet link:
# ifconfig eth0
eth0 Link encap:Ethernet HWaddr 10:10:10:10:26
inet addr:10.10.0.100 Bcast:10.10.0.255 Mask:255.255.255.0
...
# ping 10.10.0.1
PING 10.10.0.1 (10.10.0.1): 56(84) bytes of data.
64 bytes from 10.10.0.1: icmp_seq=1 ttl=31 time=0.063 ms
64 bytes from 10.10.0.1: icmp_seq=2 ttl=31 time=0.062 ms
64 bytes from 10.10.0.1: icmp_seq=3 ttl=31 time=0.063 ms
The previous section described how to use a DEC21140 device to communicate with
an Ethernet network from Firststeps machine. In the setup, all Ethernet
communication goes via a PHY chip, represented by the generic_eth_phy device.
A PHY chip takes care of the physical layer of the Ethernet protocol; its
primary task is to redirect traffic between a media access controller (MAC)
device and an Ethernet network. The DEC21140A is one example of a MAC device;
the Ethernet network is represented by an Ethernet link in Simics. Below
interfaces are used to support those behaviors:
ethernet_common, connecting to the outside Ethernet PHY device, this
interface can be left unconnected, but the outside Ethernet PHY device to be
connected must implement the ethernet_common interface.ethernet_cable, to handle the link status of the peer.ieee_802_3_mac or ieee_802_3_mac_v3, connecting to the Ethernet MAC
device, at least one of the two interfaces should be connected, if both are
connected, only use the ieee_802_3_mac_v3 interface.ieee_802_3_phy, ieee_802_3_phy_v2 and
ieee_802_3_phy_v3, to handle access from the Ethernet MAC device. All of the
three interfaces have to be implemented simultaneously to support any of the
possible interfaces call, yet internally they share the same implementation.The generic_eth_phy device can be configured by target software via the Media
Independent Interface (MII), which in Simics is represented by the
mii_management interface.
The source code of the generic_eth_phy module is sparsely commented but rather
simple. Most of the relevant documentation can be found in the IEEE 802.3
standard—in particular chapter 22, where the MII registers are specified.
The generic_eth_phy model is complete enough to be detected and configured
correctly by most operating systems. Much of the configuration of a PHY controls
which speed to use. The model ignores these settings; instead, the attribute
tx_bandwidth can be used to manually configure the maximum transmission speed.
The PHY functionality has been divided between two files:
ieee_phy.dmlieee_mii_regs.dmlThese files cannot be used by themselves; they need to be imported by a DML file
that defines the device. The files have some additional requirements, which are
documented in comments. The file generic_eth_phy.dml combines the two DML
files into the generic_eth_phy device.
This model provides some functional tests in test sub-directory, which are
helpful to verify the functionalities implementation, each test, as we suggest,
only tests one functionality and is named on its testing purpose. For more
details about how to write tests, please refer to chapter
17.