blob: 855be9892e78ea0afdbed67eb9f88fd5ada1e20f [file] [log] [blame]
//
// -------------------------------------------------------------
// Copyright 2004-2008 Synopsys, Inc.
// All Rights Reserved Worldwide
//
// Licensed under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in
// writing, software distributed under the License is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See
// the License for the specific language governing
// permissions and limitations under the License.
// -------------------------------------------------------------
//
class eth_mac_cfg;
rand bit [47:0] addr;
rand bit promiscuous;
rand bit full_duplex;
rand int unsigned rate;
rand time slot_time; // In local timeunits (ns)
rand time ifg; // In local timeunits (ns)
rand time ifg_part1; // In local timeunits (ns)
rand int unsigned attempt_limit;
rand int unsigned backoff_limit;
constraint local_unicast_addr {
addr[47:46] == 2'b00;
}
constraint eth_mac_cfg_valid {
rate inside {1, 10, 100, 1000};
ifg_part1 < ifg;
}
constraint default_values {
if (rate == 1) {
slot_time == 512 * 1000;
ifg == 96000;
ifg_part1 == ifg * 2 / 3;
attempt_limit == 16;
backoff_limit == 10;
}
if (rate == 10) {
slot_time == 512 * 100;
ifg == 9600;
ifg_part1 == ifg * 2 / 3;
attempt_limit == 16;
backoff_limit == 10;
}
if (rate == 100) {
slot_time == 512 * 10;
ifg == 960;
ifg_part1 == ifg * 2 / 3;
attempt_limit == 16;
backoff_limit == 10;
}
if (rate == 1000) {
slot_time == 4096 * 1;
ifg == 96;
ifg_part1 == ifg * 2 / 3;
attempt_limit == 16;
backoff_limit == 10;
}
}
constraint supported {
rate != 1000;
}
function string psdisplay(string prefix);
$sformat(psdisplay, "%sMAC Addr: %h.%h.%h.%h.%h.%h", prefix,
addr[47:40], addr[39:32], addr[31:24], addr[23:16], addr[15:8], addr[7:0]);
$sformat(psdisplay, "%s\n%s%0s%0s duplex, %0d Mb/s", psdisplay, prefix,
(this.promiscuous) ? "promiscuous, " : "",
(this.full_duplex) ? "full" : "half", this.rate);
$sformat(psdisplay, "%s\n%sSlotTime=%0d, ifg=%0d/%0d, %0d attempts, %0d backOff", psdisplay, prefix,
this.slot_time, this.ifg, this.ifg_part1, this.attempt_limit, this.backoff_limit);
endfunction
endclass: eth_mac_cfg
class eth_frame_tx_status extends vmm_data;
static vmm_log log = new("eth_frame_tx_status", "class");
bit successful;
int unsigned n_attempts;
function new();
super.new(this.log);
endfunction: new
endclass: eth_frame_tx_status
typedef class eth_mac;
virtual class eth_mac_callbacks extends vmm_xactor_callbacks;
virtual task pre_frame_tx(eth_mac xactor,
ref eth_frame frame,
ref bit drop);
endtask
virtual task pre_frame_tx_attempt(eth_mac xactor,
eth_frame frame,
int unsigned attempt_count,
ref bit drop);
endtask
virtual task post_frame_tx(eth_mac xactor,
eth_frame frame,
eth_frame_tx_status status);
endtask
virtual function void post_frame_rx(eth_mac xactor,
eth_frame frame);
endfunction
endclass: eth_mac_callbacks
class eth_mac extends vmm_xactor;
eth_frame_channel tx_chan;
eth_frame_channel rx_chan;
eth_frame_channel pls_tx_chan;
eth_frame_channel pls_rx_chan;
// Example 4-82
eth_pls_indications indications;
local eth_mac_cfg cfg;
local eth_mac_cfg hard_rst_cfg;
// Example 4-83
function new(string inst,
int unsigned stream_id = -1,
eth_mac_cfg cfg,
eth_frame_channel tx_chan = null,
eth_frame_channel rx_chan = null,
eth_frame_channel pls_tx_chan = null,
eth_frame_channel pls_rx_chan = null,
eth_pls_indications indications = null);
super.new("Ethernet MAC Layer", inst, stream_id);
this.cfg = cfg;
if (tx_chan == null) tx_chan = new({this.log.get_name(), " Tx frames"}, inst);
this.tx_chan = tx_chan;
if (rx_chan == null) rx_chan = new({this.log.get_name(), " Rx frames"}, inst);
this.rx_chan = rx_chan;
if (pls_tx_chan == null) pls_tx_chan = new({this.log.get_name(), " PHY->MAC frames"}, inst);
this.pls_tx_chan = pls_tx_chan;
if (pls_rx_chan == null) pls_rx_chan = new({this.log.get_name(), " MAC->PHY frames"}, inst);
this.pls_rx_chan = pls_rx_chan;
if (indications == null) indications = new(this.log);
this.indications = indications;
this.hard_rst_cfg = cfg;
endfunction: new
extern virtual function void reconfigure(eth_mac_cfg cfg);
extern virtual function void reset_xactor(reset_e rst_typ = SOFT_RST);
extern protected virtual task main();
extern local task tx_driver();
extern local task rx_monitor();
local int unsigned maxBackOff;
extern local task back_off(int unsigned n_attempts);
local bit transmitting;
local event transmitting_e;
local bit deferring;
local event deferring_e;
extern local task deference();
extern local task defer();
endclass: eth_mac
typedef class eth_mac_monitor;
virtual class eth_mac_monitor_callbacks extends vmm_xactor_callbacks;
typedef enum {TO_MAC, TO_PHY} directions_e;
virtual function void post_frame_rx(eth_mac_monitor xactor,
eth_mac_monitor_callbacks::directions_e direction,
eth_frame frame);
endfunction
endclass: eth_mac_monitor_callbacks
class eth_mac_monitor extends vmm_xactor;
eth_frame_channel to_phy_chan;
eth_frame_channel to_mac_chan;
eth_frame_channel pls_to_phy_chan;
eth_frame_channel pls_to_mac_chan;
eth_pls_indications indications;
local eth_mac_cfg cfg;
local eth_mac_cfg hard_rst_cfg;
function new(string inst,
int unsigned stream_id = -1,
eth_mac_cfg cfg,
eth_frame_channel to_phy_chan = null,
eth_frame_channel to_mac_chan = null,
eth_frame_channel pls_to_phy_chan = null,
eth_frame_channel pls_to_mac_chan = null,
eth_pls_indications indications = null);
super.new("Ethernet MAC Layer Monitor", inst, stream_id);
this.cfg = cfg;
if (to_phy_chan == null) to_phy_chan = new({this.log.get_name(), " ->MAC frames"}, inst);
this.to_phy_chan = to_phy_chan;
if (to_mac_chan == null) to_mac_chan = new({this.log.get_name(), " <-MAC frames"}, inst);
this.to_mac_chan = to_mac_chan;
if (pls_to_phy_chan == null) pls_to_phy_chan = new({this.log.get_name(), " PHY->MAC frames"}, inst);
this.pls_to_phy_chan = pls_to_phy_chan;
if (pls_to_mac_chan == null) pls_to_mac_chan = new({this.log.get_name(), " MAC->PHY frames"}, inst);
this.pls_to_mac_chan = pls_to_mac_chan;
if (indications == null) indications = new(this.log);
this.indications = indications;
this.hard_rst_cfg = cfg;
endfunction: new
extern virtual function void reconfigure(eth_mac_cfg cfg);
extern virtual function void reset_xactor(reset_e rst_typ = SOFT_RST);
extern protected virtual task main();
extern protected task to_phy_monitor();
extern protected task to_mac_monitor();
endclass: eth_mac_monitor
//
// eth_mac
//
function void eth_mac::reconfigure(eth_mac_cfg cfg);
this.cfg = cfg;
endfunction: reconfigure
function void eth_mac::reset_xactor(reset_e rst_typ = SOFT_RST);
super.reset_xactor(rst_typ);
this.tx_chan.flush();
this.rx_chan.flush();
this.pls_tx_chan.flush();
this.pls_rx_chan.flush();
this.indications.reset();
if (rst_typ >= FIRM_RST) begin
this.indications.reset(, vmm_notify::HARD );
end
if (rst_typ >= HARD_RST) begin
this.cfg = this.hard_rst_cfg;
end
endfunction: reset_xactor
task eth_mac::main();
fork
super.main();
this.tx_driver();
this.rx_monitor();
this.deference();
join_none
endtask: main
task eth_mac::tx_driver();
eth_frame fr;
while (1) begin
bit do_continue = 0;
bit do_break = 0;
bit drop = 0;
eth_frame_tx_status status = new;
do_continue = 0;
status.successful = 0;
status.n_attempts = 0;
this.transmitting = 0;
this.wait_if_stopped_or_empty(this.tx_chan);
this.tx_chan.activate(fr);
`vmm_callback(eth_mac_callbacks,
pre_frame_tx(this,
fr,
drop));
if (drop) begin
void'(this.tx_chan.remove());
fr.notify.indicate(vmm_data::ENDED , status);
do_continue = 1;
end
if (!do_continue) begin
void'(this.tx_chan.start());
while (status.n_attempts < this.cfg.attempt_limit && !status.successful && !do_break) begin
bit col = 0;
`vmm_callback(eth_mac_callbacks,
pre_frame_tx_attempt(this,
fr,
status.n_attempts,
drop));
if (drop) do_break=1;
if (!do_break) begin
if (log.start_msg(vmm_log::DEBUG_TYP , vmm_log::TRACE_SEV )) begin
string prefix;
void'(log.text("Attempting to transmitting frame..."));
$sformat(prefix, " Attempt #%0d: ", status.n_attempts);
void'(log.text(fr.psdisplay(prefix)));
log.end_msg();
end
if (status.n_attempts > 0) this.back_off(status.n_attempts);
this.defer();
// Watch for collisions
if (!this.cfg.full_duplex) begin
fork
begin
this.indications.wait_for(eth_pls_indications::COLLISION);
col = 1;
`vmm_debug(this.log, "Collision!");
end
join_none
end
// PLS Tx channel has a blocking model
this.transmitting = 1;
this.pls_tx_chan.put(fr);
this.transmitting = 0;
disable fork;
status.n_attempts++;
// If there were no collisions, success!
if (!col) begin
status.successful = 1;
do_break=1;
end
end // if !do_break
end
if (log.start_msg(vmm_log::DEBUG_TYP , vmm_log::TRACE_SEV )) begin
string prefix;
void'(log.text("Done transmitting frame..."));
$sformat(prefix, " %0s: ", (status.successful) ? "Success" : "Failure");
void'(log.text(fr.psdisplay(prefix)));
log.end_msg();
end
void'(this.tx_chan.complete());
`vmm_callback(eth_mac_callbacks,
post_frame_tx(this,
fr,
status));
fr.notify.indicate(vmm_data::ENDED , status);
void'(this.tx_chan.remove());
end // if !do_continue
end
endtask: tx_driver
task eth_mac::rx_monitor();
eth_frame fr;
while (1) begin
this.pls_rx_chan.get(fr);
`vmm_callback(eth_mac_callbacks,
post_frame_rx(this,
fr));
if (log.start_msg(vmm_log::DEBUG_TYP , vmm_log::TRACE_SEV )) begin
void'(log.text("Received frame..."));
void'(log.text(fr.psdisplay(" ")));
log.end_msg();
end
if (this.cfg.promiscuous ||
this.cfg.addr === fr.dst ||
fr.dst[47] === 1'b1) begin
this.rx_chan.sneak(fr);
end else begin
`vmm_debug(this.log, "Frame ignored because of wrong unicast address");
end
end
endtask: rx_monitor
task eth_mac::back_off(int unsigned n_attempts);
int unsigned n;
if (n_attempts == 1) this.maxBackOff = 2;
else if (n_attempts < this.cfg.backoff_limit) this.maxBackOff *= 2;
n = $random % this.maxBackOff;
`vmm_debug(this.log, $psprintf("Backing off for %0d slotTimes...", n));
#(this.cfg.slot_time * n);
endtask: back_off
task eth_mac::deference();
forever begin
this.deferring = 0;
`vmm_debug(this.log, "Deference: not active");
if (this.cfg.full_duplex) begin
wait (this.transmitting === 1'b1);
this.deferring = 1;
wait (this.transmitting === 1'b0);
#(this.cfg.ifg);
end else begin
bit wasTransmitting;
`vmm_debug(this.log, "Deference: Waiting for CRS");
this.indications.wait_for(eth_pls_indications::CARRIER);
wasTransmitting = this.transmitting;
this.deferring = 1;
`vmm_debug(this.log, "Deference: ACTIVE. Waiting for ~CRS");
this.indications.wait_for_off(eth_pls_indications::CARRIER);
wait (this.transmitting === 1'b0);
`vmm_debug(this.log, "Deference: IFG");
if (wasTransmitting) begin
#(this.cfg.ifg_part1);
end else begin: interFrameSpacingPart1
forever begin
fork
begin
#(this.cfg.ifg_part1);
disable interFrameSpacingPart1;
end
join_none
this.indications.wait_for(eth_pls_indications::CARRIER);
disable fork;
this.indications.wait_for_off(eth_pls_indications::CARRIER);
end
end
#(this.cfg.ifg - this.cfg.ifg_part1);
end
end
endtask: deference
task eth_mac::defer();
`vmm_debug(this.log, "Deferring...");
wait(this.deferring === 1'b0);
`vmm_debug(this.log, "Deferred.");
endtask: defer
//
// eth_mac_monitor
//
function void eth_mac_monitor::reconfigure(eth_mac_cfg cfg);
this.cfg = cfg;
endfunction: reconfigure
function void eth_mac_monitor::reset_xactor(reset_e rst_typ = SOFT_RST);
super.reset_xactor(rst_typ);
this.to_phy_chan.flush();
this.to_mac_chan.flush();
this.pls_to_phy_chan.flush();
this.pls_to_mac_chan.flush();
this.indications.reset();
if (rst_typ >= FIRM_RST) begin
this.indications.reset(, vmm_notify::HARD );
end
if (rst_typ >= HARD_RST) begin
this.cfg = this.hard_rst_cfg;
end
endfunction: reset_xactor
task eth_mac_monitor::main();
fork
super.main();
this.to_phy_monitor();
this.to_mac_monitor();
join_none
endtask: main
task eth_mac_monitor::to_phy_monitor();
eth_frame fr;
while (1) begin
this.pls_to_phy_chan.get(fr);
`vmm_callback(eth_mac_monitor_callbacks,
post_frame_rx(this,
eth_mac_monitor_callbacks::TO_PHY,
fr));
this.to_phy_chan.sneak(fr);
end
endtask: to_phy_monitor
task eth_mac_monitor::to_mac_monitor();
eth_frame fr;
while (1) begin
this.pls_to_mac_chan.get(fr);
`vmm_callback(eth_mac_monitor_callbacks,
post_frame_rx(this,
eth_mac_monitor_callbacks::TO_MAC,
fr));
this.to_mac_chan.sneak(fr);
end
endtask: to_mac_monitor