From c44dfcf3195218d02e947ea8060dc8377eaefacf Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Thu, 26 Feb 2026 13:48:52 -0500 Subject: [PATCH 1/7] Add Wishbone manager Verilog DUT implementation --- protocols/tests/wishbone_manager/hbexec.v | 439 ++++++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100644 protocols/tests/wishbone_manager/hbexec.v diff --git a/protocols/tests/wishbone_manager/hbexec.v b/protocols/tests/wishbone_manager/hbexec.v new file mode 100644 index 00000000..9df25778 --- /dev/null +++ b/protocols/tests/wishbone_manager/hbexec.v @@ -0,0 +1,439 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: hbexec.v +// {{{ +// Project: dbgbus, a collection of 8b channel to WB bus debugging protocols +// +// Purpose: An example debug bus. This bus takes commands from an incoming +// 34-bit command word, and issues those commands across a wishbone +// bus, returning the result from the bus command. Basic bus commands are: +// +// 2'b00 Read +// 2'b01 Write (lower 32-bits are the value to be written) +// 2'b10 Set address +// Next 30 bits are the address +// bit[1] is an address difference bit +// bit[0] is an increment bit +// 2'b11 Special command +// +// In the interests of code simplicity, this memory operator is +// susceptible to unknown results should a new command be sent to it +// before it completes the last one. Unpredictable results might then +// occurr. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2017-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the hexbus debugging interface. +// +// The hexbus interface is free software (firmware): you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public License +// as published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// The hexbus interface is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +// for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. (It's in the $(ROOT)/doc directory. Run make +// with no target there if the PDF file isn't present.) If not, see +// for a copy. +// }}} +// License: LGPL, v3, as defined and found on www.gnu.org, +// {{{ +// http://www.gnu.org/licenses/lgpl.html +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// +`define CMD_SUB_RD 2'b00 +`define CMD_SUB_WR 2'b01 +`define CMD_SUB_BUS 1'b0 +`define CMD_SUB_ADDR 2'b10 +`define CMD_SUB_SPECIAL 2'b11 +// +`define RSP_SUB_DATA 2'b00 +`define RSP_SUB_ACK 2'b01 +`define RSP_SUB_ADDR 2'b10 +`define RSP_SUB_SPECIAL 2'b11 +// +`define RSP_WRITE_ACKNOWLEDGEMENT { `RSP_SUB_ACK, 32'h0 } +`define RSP_RESET { `RSP_SUB_SPECIAL, 3'h0, 29'h00 } +`define RSP_BUS_ERROR { `RSP_SUB_SPECIAL, 3'h1, 29'h00 } +// }}} +module hbexec #( + // {{{ + parameter ADDRESS_WIDTH=30, + localparam AW=ADDRESS_WIDTH, // Shorthand for address width + CW=34 // Command word width + // }}} + ) ( + // {{{ + input wire i_clk, i_reset, + // The input command channel + // {{{ + input wire i_cmd_stb, + input wire [(CW-1):0] i_cmd_word, + output wire o_cmd_busy, + // }}} + // The return command channel + // {{{ + output reg o_rsp_stb, + output reg [(CW-1):0] o_rsp_word, + // }}} + // Wishbone outputs + // {{{ + output reg o_wb_cyc, o_wb_stb, o_wb_we, + output reg [(AW-1):0] o_wb_addr, + output reg [31:0] o_wb_data, + output wire [3:0] o_wb_sel, + // }}} + // Wishbone inputs + // {{{ + input wire i_wb_stall, i_wb_ack, i_wb_err, + input wire [31:0] i_wb_data + // }}} + // }}} + ); + + // Local declarations + // {{{ + reg newaddr, inc; + wire i_cmd_addr, i_cmd_wr, i_cmd_rd, i_cmd_bus; + // }}} + + // + // Decode our input commands + // {{{ + assign i_cmd_addr = (i_cmd_stb)&&(i_cmd_word[33:32] == `CMD_SUB_ADDR); + assign i_cmd_rd = (i_cmd_stb)&&(i_cmd_word[33:32] == `CMD_SUB_RD); + assign i_cmd_wr = (i_cmd_stb)&&(i_cmd_word[33:32] == `CMD_SUB_WR); + assign i_cmd_bus = (i_cmd_stb)&&(i_cmd_word[33] == `CMD_SUB_BUS); + // }}} + + // o_wb_cyc, o_wb_stb + // {{{ + // These two linse control our state + initial o_wb_cyc = 1'b0; + initial o_wb_stb = 1'b0; + always @(posedge i_clk) + if ((i_reset)||((i_wb_err)&&(o_wb_cyc))) + begin + // On any error or reset, then clear the bus. + o_wb_cyc <= 1'b0; + o_wb_stb <= 1'b0; + end else if (o_wb_stb) + begin + // + // BUS REQUEST state + // + if (!i_wb_stall) + // If we are only going to do one transaction, + // then as soon as the stall line is lowered, we are + // done. + o_wb_stb <= 1'b0; + + // While not likely, it is possible that a slave might ACK + // our request on the same clock it is received. In that + // case, drop the CYC line. + // + // We gate this with the stall line in case we receive an + // ACK while our request has yet to go out. This may make + // more sense later, when we are sending multiple back to back + // requests across the bus, but we'll leave this gate here + // as a placeholder until then. + if ((!i_wb_stall)&&(i_wb_ack)) + o_wb_cyc <= 1'b0; + end else if (o_wb_cyc) + begin + // + // BUS WAIT + // + if (i_wb_ack) + // Once the slave acknowledges our request, we are done. + o_wb_cyc <= 1'b0; + end else begin + // + // IDLE state + // + if (i_cmd_bus) + begin + // We've been asked to start a bus cycle from our + // command word, either RD or WR + o_wb_cyc <= 1'b1; + o_wb_stb <= 1'b1; + end + end + // }}} + + // For now, we'll use the bus cycle line as an indication of whether + // or not we are too busy to accept anything else from the command + // port. This will change if we want to accept multiple write + // commands per bus cycle, but that will be a bus master that's + // not nearly so simple. + assign o_cmd_busy = o_wb_cyc; + + + // + // The bus WE (write enable) line, governing wishbone direction + // + // We'll never change direction mid bus-cycle--at least not in this + // implementation (atomic accesses may require it at a later date). + // Hence, if CYC is low we can set the direction. + always @(posedge i_clk) + if (!o_wb_cyc) + o_wb_we <= (i_cmd_wr); + + // o_wb_addr + // {{{ + // The bus ADDRESS lines + // + initial newaddr = 1'b0; + always @(posedge i_clk) + begin + if ((i_cmd_addr)&&(!o_cmd_busy)) + begin + // If we are in the idle state, we accept address + // setting commands. Specifically, we'll allow the + // user to either set the address, or add a difference + // to our address. The difference may not make sense + // now, but if we ever wish to compress our command bus, + // sending an address difference can drastically cut + // down the number of bits required in a set address + // request. + if (!i_cmd_word[1]) + o_wb_addr <= i_cmd_word[AW+1:2]; + else + o_wb_addr <= i_cmd_word[AW+1:2] + o_wb_addr; + + // + // We'll allow that bus requests can either increment + // the address, or leave it the same. One bit in the + // command word will tell us which, and we'll set this + // bit on any set address command. + inc <= !i_cmd_word[0]; + end else if ((o_wb_stb)&&(!i_wb_stall)) + // The address lines are used while the bus is active, + // and referenced any time STB && !STALL are true. + // + // However, once STB and !STALL are both true, then the + // bus is ready to move to the next request. Hence, + // we add our increment (one or zero) here. + o_wb_addr <= o_wb_addr + {{(AW-1){1'b0}}, inc}; + + + // We'd like to respond to the bus with any address we just + // set. The goal here is that, upon any read from the bus, + // we should be able to know what address the bus was set to. + // For this reason, we want to return the bus address up the + // command stream. + // + // The problem is that the add (above) when setting the address + // takes a clock to do. Hence, we'll use "newaddr" as a flag + // that o_wb_addr has a new value in it that needs to be + // returned via the command link. + newaddr <= ((!i_reset)&&(i_cmd_addr)&&(!o_cmd_busy)); + end + // }}} + + // + // The bus DATA (output) lines + // + + // o_wb_data + // {{{ + always @(posedge i_clk) + begin + // This may look a touch confusing ... what's important is that: + // + // 1. No one cares what the bus data lines are, unless we are + // in the middle of a write cycle. + // 2. Even during a write cycle, these lines are don't cares + // if the STB line is low, indicating no more requests + // 3. When a request is received to write, and so we transition + // to a bus write cycle, that request will come with data. + // 4. Hence, we set the data words in the IDLE state on the + // same clock we go to BUS REQUEST. While in BUS REQUEST, + // these lines cannot change until the slave has accepted + // our inputs. + // + // Thus we force these lines to be constant any time STB and + // STALL are both true, but set them otherwise. + // + if ((!o_wb_stb)||(!i_wb_stall)) + o_wb_data <= i_cmd_word[31:0]; + end + // }}} + + // + // For this command bus channel, we'll only ever direct word addressing. + // + assign o_wb_sel = 4'hf; + + // + // The COMMAND RESPONSE return channel + // + + // o_rsp_stb, o_rsp_word + // {{{ + // This is where we set o_rsp_stb and o_rsp_word for the return channel. + // The logic is set so that o_rsp_stb will be true for any one clock + // where we have data to reutrn, and zero otherwise. If o_rsp_stb is + // true, then o_rsp_word is the response we want to return. In all + // other cases, o_rsp_word is a don't care. + initial o_rsp_stb = 1'b1; + initial o_rsp_word = `RSP_RESET; + always @(posedge i_clk) + if (i_reset) + begin + o_rsp_stb <= 1'b1; + o_rsp_word <= `RSP_RESET; + end else if (i_wb_err) + begin + o_rsp_stb <= 1'b1; + o_rsp_word <= `RSP_BUS_ERROR; + end else if (o_wb_cyc) begin + // + // We're either in the BUS REQUEST or BUS WAIT states + // + // Either way, we want to return a response on our command + // channel if anything gets ack'd + o_rsp_stb <= (i_wb_ack); + // + // + if (o_wb_we) + o_rsp_word <= `RSP_WRITE_ACKNOWLEDGEMENT; + else + o_rsp_word <= { `RSP_SUB_DATA, i_wb_data }; + end else begin + // + // We are in the IDLE state. + // + // Echo any new addresses back up the command chain + // + o_rsp_stb <= newaddr; + o_rsp_word <= { `RSP_SUB_ADDR, + {(30-ADDRESS_WIDTH){1'b0}}, o_wb_addr, 1'b0, !inc }; + end + // }}} + + // Make Verilator happy + // {{{ + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, i_cmd_rd }; + // verilator lint_on UNUSED + // }}} +`ifdef FORMAL +`ifdef HBEXEC +`define ASSUME assume +`define ASSERT assert +`else +`define ASSUME assert +`define ASSERT assert +`endif + + reg f_past_valid; + initial f_past_valid = 1'b0; + always @(posedge i_clk) + f_past_valid <= 1'b1; + + initial `ASSUME(i_reset); + + localparam F_LGDEPTH=2; + wire [F_LGDEPTH-1:0] f_nreqs, f_nacks, f_outstanding; + + fwb_master #( .AW(AW),.F_MAX_STALL(3),.F_MAX_ACK_DELAY(3), + .F_LGDEPTH(F_LGDEPTH) + ) fwbi(i_clk, i_reset, + o_wb_cyc, o_wb_stb, o_wb_we, o_wb_addr, o_wb_data, + o_wb_sel, i_wb_ack, i_wb_stall, i_wb_data, + i_wb_err, + f_nreqs, f_nacks, f_outstanding); + + always @(posedge i_clk) + if ((f_outstanding > 1)||(o_wb_stb)) + assume(!i_cmd_stb); + + always @(posedge i_clk) + if ((!f_past_valid)||($past(i_reset))) + begin + `ASSUME(!i_cmd_stb); + // + `ASSERT(o_rsp_stb); + `ASSERT(o_rsp_word == `RSP_RESET); + end + + always @(*) + if (o_wb_cyc) + `ASSERT(o_cmd_busy); + + always @(posedge i_clk) + if ((f_past_valid)&&($past(o_cmd_busy))&&($past(i_cmd_stb))) + begin + assume($stable(i_cmd_stb)); + assume($stable(i_cmd_word)); + end + + always @(posedge i_clk) + if ((f_past_valid)&&(!$past(i_reset)) + &&($past(i_cmd_addr))&&(!$past(o_cmd_busy))) + begin + `ASSERT(newaddr); + if ($past(i_cmd_word[1])) + `ASSERT(o_wb_addr == $past(i_cmd_word[AW+1:2]+o_wb_addr)); + else + `ASSERT(o_wb_addr == $past(i_cmd_word[AW+1:2])); + end + + always @(posedge i_clk) + if ((f_past_valid)&&(!$past(i_reset))&&($past(newaddr))) + begin + `ASSERT(o_rsp_stb == 1'b1); + `ASSERT(o_rsp_word == { `RSP_SUB_ADDR, + $past(o_wb_addr), 1'b0, !$past(inc) }); + end + + always @(posedge i_clk) + if ((f_past_valid)&&($past(o_wb_cyc))&&(o_wb_cyc)) + `ASSERT($stable(o_wb_we)); + + always @(posedge i_clk) + if ((f_past_valid)&&(!$past(i_reset)) + &&($past(o_wb_cyc))&&($past(i_wb_ack))) + begin + if ($past(o_wb_we)) + `ASSERT(o_rsp_word == `RSP_WRITE_ACKNOWLEDGEMENT); + else + `ASSERT(o_rsp_word == { `RSP_SUB_DATA, $past(i_wb_data) }); + end + + always @(posedge i_clk) + if ((f_past_valid)&&($past(i_reset))) + `ASSERT((o_rsp_stb)&&(o_rsp_word == `RSP_RESET)); + + always @(posedge i_clk) + if ((f_past_valid)&&(!$past(i_reset)) + &&($past(o_wb_cyc))&&($past(i_wb_err))) + `ASSERT((o_rsp_stb)&&(o_rsp_word == `RSP_BUS_ERROR)); + + always @(posedge i_clk) + if ((f_past_valid)&&(!$past(i_reset)) + &&($past(i_cmd_addr))&&(!$past(o_cmd_busy))) + begin + if ($past(i_cmd_wr)) + `ASSERT((o_wb_cyc)&&(o_wb_stb)&&(o_wb_we)); + if ($past(i_cmd_rd)) + `ASSERT((o_wb_cyc)&&(o_wb_stb)&&(!o_wb_we)); + end + +`endif +endmodule From 5dda1fd03effddc93e2c15eb3112a1adc8e60e82 Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Thu, 26 Feb 2026 14:45:37 -0500 Subject: [PATCH 2/7] Add comments to Verilog Wishbone manager code --- protocols/tests/wishbone_manager/hbexec.v | 233 ++++++++++++++-------- 1 file changed, 148 insertions(+), 85 deletions(-) diff --git a/protocols/tests/wishbone_manager/hbexec.v b/protocols/tests/wishbone_manager/hbexec.v index 9df25778..ad852864 100644 --- a/protocols/tests/wishbone_manager/hbexec.v +++ b/protocols/tests/wishbone_manager/hbexec.v @@ -53,22 +53,41 @@ // // `default_nettype none -// -`define CMD_SUB_RD 2'b00 -`define CMD_SUB_WR 2'b01 -`define CMD_SUB_BUS 1'b0 -`define CMD_SUB_ADDR 2'b10 -`define CMD_SUB_SPECIAL 2'b11 -// -`define RSP_SUB_DATA 2'b00 -`define RSP_SUB_ACK 2'b01 -`define RSP_SUB_ADDR 2'b10 -`define RSP_SUB_SPECIAL 2'b11 -// + +/* -------------------------------------------------------------------------- */ +/* Request "opcodes" */ +/* -------------------------------------------------------------------------- */ + +`define CMD_SUB_RD 2'b00 // Read request +`define CMD_SUB_WR 2'b01 // Write request +`define CMD_SUB_BUS 1'b0 // A request in general +`define CMD_SUB_ADDR 2'b10 // Set an address +`define CMD_SUB_SPECIAL 2'b11 // Bus reset + +/* -------------------------------------------------------------------------- */ +/* Response "opcodes" */ +/* -------------------------------------------------------------------------- */ + +`define RSP_SUB_DATA 2'b00 // Result of data read from the bus +`define RSP_SUB_ACK 2'b01 // Acknowledgement +`define RSP_SUB_ADDR 2'b10 // Acknowledge new address +`define RSP_SUB_SPECIAL 2'b11 // Either a bus reset, or an error (see below) + +/* -------------------------------------------------------------------------- */ +/* Possible responses */ +/* -------------------------------------------------------------------------- */ +// Write acknowledgement `define RSP_WRITE_ACKNOWLEDGEMENT { `RSP_SUB_ACK, 32'h0 } +// Bus reset +// (This causes us to abandon any bus cycle we may be in the middle of) `define RSP_RESET { `RSP_SUB_SPECIAL, 3'h0, 29'h00 } +// Bus error `define RSP_BUS_ERROR { `RSP_SUB_SPECIAL, 3'h1, 29'h00 } -// }}} + +/* -------------------------------------------------------------------------- */ +/* The actual manager module */ +/* -------------------------------------------------------------------------- */ + module hbexec #( // {{{ parameter ADDRESS_WIDTH=30, @@ -76,69 +95,115 @@ module hbexec #( CW=34 // Command word width // }}} ) ( - // {{{ input wire i_clk, i_reset, - // The input command channel - // {{{ + + /* ------------------------------------------------------------------------ */ + /* The input command channel */ + /* ------------------------------------------------------------------------ */ + input wire i_cmd_stb, - input wire [(CW-1):0] i_cmd_word, + + // `i_cmd_word` is a 34-bit word containing the command (read/write, etc.) + // We use the top 2 bits of the 34-bit word to indicate what type + // of command we're issuing. + // We then use the bottom 32 bits for passing data values. + // The encoding is as follows: + // 33 32 31 - 0 + // 0 0 Read request, ignore the rest of the 32-bits, ACK on output + // 0 1 Write request, the 32-bit data contains the word to be written + // 1 0 Set an address. If bit 31 is set, we’ll add this value to the current bus address. + // If bit 30 is set, the address will be incremented upon each bus access + // 1 1 4’h0, 28’hxx, Bus Reset + input wire [(CW-1):0] i_cmd_word, + + // `o_cmd_busy` is true any time the bus is active output wire o_cmd_busy, - // }}} - // The return command channel - // {{{ + + + /* ------------------------------------------------------------------------ */ + /* The return command channel */ + /* ------------------------------------------------------------------------ */ + // `o_rsp_stb` is true whenever the output references a valid codeword output reg o_rsp_stb, + + // The 34-bit output codeword, encoded as follows: + // 33 32 31 - 0 + // 0 0 Acknowledge a write. The 32-bit value contains number of writes to acknowledge + // 0 1 Read response, the 32 data bits are the word that was read + // 1 0 Acknowledge an address that has been set, with two zero bits and 30 address bits + // 1 1 N/A + // 1 1 3’h0, 29’hxx, Bus Reset acknowledgement + // 1 1 3’h1, 29’hxx, Bus Error output reg [(CW-1):0] o_rsp_word, - // }}} - // Wishbone outputs - // {{{ + + /* ------------------------------------------------------------------------ */ + /* Wishbone outputs */ + /* ------------------------------------------------------------------------ */ output reg o_wb_cyc, o_wb_stb, o_wb_we, output reg [(AW-1):0] o_wb_addr, output reg [31:0] o_wb_data, output wire [3:0] o_wb_sel, - // }}} - // Wishbone inputs - // {{{ + + /* ------------------------------------------------------------------------ */ + /* Wishbone inputs */ + /* ------------------------------------------------------------------------ */ + input wire i_wb_stall, i_wb_ack, i_wb_err, input wire [31:0] i_wb_data - // }}} - // }}} ); // Local declarations - // {{{ reg newaddr, inc; wire i_cmd_addr, i_cmd_wr, i_cmd_rd, i_cmd_bus; - // }}} - // - // Decode our input commands - // {{{ + /* ------------------------------------------------------------------------- */ + /* Decode input commands */ + /* ------------------------------------------------------------------------- */ + + // Decode our input commands (represented using a 34-bit word `i_cmd_word`) + // Input commands are valid only when `i_cmd_stb` is valid, + // and the commands are accepted only when `o_cmd_busy` is false + + // Set address + // If bit 31 is set, we add the value to the current bus address + // If bit 30 is set, the address is incremented upon each bus access assign i_cmd_addr = (i_cmd_stb)&&(i_cmd_word[33:32] == `CMD_SUB_ADDR); + + // Read request (ignore lower 32 bits of word) assign i_cmd_rd = (i_cmd_stb)&&(i_cmd_word[33:32] == `CMD_SUB_RD); + + // Write request (lower 32 bits of word containing the data to be written) assign i_cmd_wr = (i_cmd_stb)&&(i_cmd_word[33:32] == `CMD_SUB_WR); + + // We use `i_cmd_bus` to capture whether we have a read or write request assign i_cmd_bus = (i_cmd_stb)&&(i_cmd_word[33] == `CMD_SUB_BUS); - // }}} - // o_wb_cyc, o_wb_stb - // {{{ - // These two linse control our state + /* ------------------------------------------------------------------------- */ + /* Logic for updating `cyc` and `stb` */ + /* ------------------------------------------------------------------------- */ + + // Initialize the `cyc` and `stb` signals to 0 at time 0 + // The `initial` keyword is only for simulation / testbench purposes initial o_wb_cyc = 1'b0; initial o_wb_stb = 1'b0; + always @(posedge i_clk) if ((i_reset)||((i_wb_err)&&(o_wb_cyc))) begin - // On any error or reset, then clear the bus. + // Set `cyc` & `stb` both to 0 on any error or reset, then clear the bus. o_wb_cyc <= 1'b0; o_wb_stb <= 1'b0; end else if (o_wb_stb) begin - // - // BUS REQUEST state - // + + /* ------------------------------------------------------------------------ */ + /* BUS REQUEST state (cyc = 1, stb = 1) */ + /* ------------------------------------------------------------------------ */ + if (!i_wb_stall) // If we are only going to do one transaction, - // then as soon as the stall line is lowered, we are - // done. + // as soon as the stall line is lowered, we are + // done, in which case set `stb = 0` o_wb_stb <= 1'b0; // While not likely, it is possible that a slave might ACK @@ -154,25 +219,28 @@ module hbexec #( o_wb_cyc <= 1'b0; end else if (o_wb_cyc) begin - // - // BUS WAIT - // + + /* ------------------------------------------------------------------------ */ + /* BUS WAIT state (cyc = 1, stb = 0) */ + /* ------------------------------------------------------------------------ */ if (i_wb_ack) // Once the slave acknowledges our request, we are done. o_wb_cyc <= 1'b0; end else begin - // - // IDLE state - // + + /* ------------------------------------------------------------------------ */ + /* IDLE state (cyc = 0, syb = 0) */ + /* ------------------------------------------------------------------------ */ + if (i_cmd_bus) begin // We've been asked to start a bus cycle from our - // command word, either RD or WR + // command word, i.e. we're performing a read/write request. + // Either way, we need to set `cyc` and `stb` both to 1 o_wb_cyc <= 1'b1; o_wb_stb <= 1'b1; end end - // }}} // For now, we'll use the bus cycle line as an indication of whether // or not we are too busy to accept anything else from the command @@ -182,20 +250,24 @@ module hbexec #( assign o_cmd_busy = o_wb_cyc; - // - // The bus WE (write enable) line, governing wishbone direction - // - // We'll never change direction mid bus-cycle--at least not in this + /* ------------------------------------------------------------------------- */ + /* Logic for detemrining the output bus WE (write enable) line */ + /* ------------------------------------------------------------------------- */ + // `o_wb_we` determines the request direction + // We only accept commands when we are in the idle state, + // and we only transition to the bus request state on a read/write command, + // so the following `always` block is sufficient. + // Also, we'll never change direction mid bus-cycle--at least not in this // implementation (atomic accesses may require it at a later date). // Hence, if CYC is low we can set the direction. always @(posedge i_clk) if (!o_wb_cyc) o_wb_we <= (i_cmd_wr); - // o_wb_addr - // {{{ - // The bus ADDRESS lines - // + + /* ------------------------------------------------------------------------- */ + /* Logic for updating `o_wb_addr` (the bus ADDRESS line) */ + /* ------------------------------------------------------------------------- */ initial newaddr = 1'b0; always @(posedge i_clk) begin @@ -214,12 +286,12 @@ module hbexec #( else o_wb_addr <= i_cmd_word[AW+1:2] + o_wb_addr; - // // We'll allow that bus requests can either increment // the address, or leave it the same. One bit in the // command word will tell us which, and we'll set this // bit on any set address command. inc <= !i_cmd_word[0]; + end else if ((o_wb_stb)&&(!i_wb_stall)) // The address lines are used while the bus is active, // and referenced any time STB && !STALL are true. @@ -242,14 +314,11 @@ module hbexec #( // returned via the command link. newaddr <= ((!i_reset)&&(i_cmd_addr)&&(!o_cmd_busy)); end - // }}} - // - // The bus DATA (output) lines - // - - // o_wb_data - // {{{ + /* ------------------------------------------------------------------------- */ + /* Logic for setting `o_wb_data` (The bus DATA (output) lines) */ + /* ------------------------------------------------------------------------- */ + always @(posedge i_clk) begin // This may look a touch confusing ... what's important is that: @@ -271,24 +340,20 @@ module hbexec #( if ((!o_wb_stb)||(!i_wb_stall)) o_wb_data <= i_cmd_word[31:0]; end - // }}} - // + // For this command bus channel, we'll only ever direct word addressing. - // assign o_wb_sel = 4'hf; - // - // The COMMAND RESPONSE return channel - // - - // o_rsp_stb, o_rsp_word - // {{{ + /* ------------------------------------------------------------------------- */ + /* Logic for updating `o_rsp_stb`, `o_rsp_word` */ + /* (The COMMAND RESPONSE return channel) */ + /* ------------------------------------------------------------------------- */ // This is where we set o_rsp_stb and o_rsp_word for the return channel. // The logic is set so that o_rsp_stb will be true for any one clock // where we have data to reutrn, and zero otherwise. If o_rsp_stb is // true, then o_rsp_word is the response we want to return. In all - // other cases, o_rsp_word is a don't care. + // other cases, o_rsp_word is DontCare. initial o_rsp_stb = 1'b1; initial o_rsp_word = `RSP_RESET; always @(posedge i_clk) @@ -301,24 +366,22 @@ module hbexec #( o_rsp_stb <= 1'b1; o_rsp_word <= `RSP_BUS_ERROR; end else if (o_wb_cyc) begin - // + // We're either in the BUS REQUEST or BUS WAIT states - // // Either way, we want to return a response on our command // channel if anything gets ack'd o_rsp_stb <= (i_wb_ack); - // - // + + // If write-enable is set, the response is a Write Acknowledgement. + // Otherwise, return the data word in the response. if (o_wb_we) o_rsp_word <= `RSP_WRITE_ACKNOWLEDGEMENT; else o_rsp_word <= { `RSP_SUB_DATA, i_wb_data }; end else begin - // + // We are in the IDLE state. - // - // Echo any new addresses back up the command chain - // + // Echo any new addresses back up the command chain. o_rsp_stb <= newaddr; o_rsp_word <= { `RSP_SUB_ADDR, {(30-ADDRESS_WIDTH){1'b0}}, o_wb_addr, 1'b0, !inc }; From 4613a6a1b7f39b37a3064670c8c76fbcfe90be15 Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Thu, 26 Feb 2026 14:52:34 -0500 Subject: [PATCH 3/7] Add struct definition --- protocols/tests/wishbone_manager/hbexec.v | 8 ++++-- .../wishbone_manager/wishbone_manager.out | 0 .../wishbone_manager/wishbone_manager.prot | 28 +++++++++++++++++++ .../wishbone_manager/wishbone_manager.tx | 0 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 protocols/tests/wishbone_manager/wishbone_manager.out create mode 100644 protocols/tests/wishbone_manager/wishbone_manager.prot create mode 100644 protocols/tests/wishbone_manager/wishbone_manager.tx diff --git a/protocols/tests/wishbone_manager/hbexec.v b/protocols/tests/wishbone_manager/hbexec.v index ad852864..c7937ece 100644 --- a/protocols/tests/wishbone_manager/hbexec.v +++ b/protocols/tests/wishbone_manager/hbexec.v @@ -137,7 +137,7 @@ module hbexec #( output reg [(CW-1):0] o_rsp_word, /* ------------------------------------------------------------------------ */ - /* Wishbone outputs */ + /* Wishbone outputs (provided to the subordinate) */ /* ------------------------------------------------------------------------ */ output reg o_wb_cyc, o_wb_stb, o_wb_we, output reg [(AW-1):0] o_wb_addr, @@ -145,7 +145,7 @@ module hbexec #( output wire [3:0] o_wb_sel, /* ------------------------------------------------------------------------ */ - /* Wishbone inputs */ + /* Wishbone inputs (obtained from the subordinate) */ /* ------------------------------------------------------------------------ */ input wire i_wb_stall, i_wb_ack, i_wb_err, @@ -253,7 +253,9 @@ module hbexec #( /* ------------------------------------------------------------------------- */ /* Logic for detemrining the output bus WE (write enable) line */ /* ------------------------------------------------------------------------- */ - // `o_wb_we` determines the request direction + // `o_wb_we` determines the request direction (i.e. whether its a read/write) + // for the Wishbone subordinate (i.e. the manager tells the subordinate + // if it should do a write or a read). // We only accept commands when we are in the idle state, // and we only transition to the bus request state on a read/write command, // so the following `always` block is sufficient. diff --git a/protocols/tests/wishbone_manager/wishbone_manager.out b/protocols/tests/wishbone_manager/wishbone_manager.out new file mode 100644 index 00000000..e69de29b diff --git a/protocols/tests/wishbone_manager/wishbone_manager.prot b/protocols/tests/wishbone_manager/wishbone_manager.prot new file mode 100644 index 00000000..1f01ff3d --- /dev/null +++ b/protocols/tests/wishbone_manager/wishbone_manager.prot @@ -0,0 +1,28 @@ +// A Wishbone manager which takes commands encoded via an input 34-bit word, +// issues those commands across a Wishbone bus, +// and returns the result from the bus to the subordinate +struct WBManager { + in i_reset: u1, + + // Input command channel (see Verilog for comments) + in i_cmd_stb: u1, + in i_cmd_word: u34, + out o_cmd_busy: u1, + + // Return command channel (see Verilog for comments) + out o_rsp_stb: u1, + out o_rsp_word: u34, + + // Wishbone outputs + out o_wb_cyc: u1, + out o_wb_stb: u1, + out o_wb_we: u1, + out o_wb_addr: u30, + out wb_data: u32, + + // Wishbone inputs + in i_wb_stall: u1, + in i_wb_ack: u1, + in i_wb_err: u1, + in i_wb_data: u32 +} diff --git a/protocols/tests/wishbone_manager/wishbone_manager.tx b/protocols/tests/wishbone_manager/wishbone_manager.tx new file mode 100644 index 00000000..e69de29b From 3ca268b44d06547eabe89c5c92f27a93fb9890c2 Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Thu, 26 Feb 2026 19:51:02 -0500 Subject: [PATCH 4/7] Add some basic protocols --- .../wishbone_manager/wishbone_manager.prot | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/protocols/tests/wishbone_manager/wishbone_manager.prot b/protocols/tests/wishbone_manager/wishbone_manager.prot index 1f01ff3d..4ad0c41c 100644 --- a/protocols/tests/wishbone_manager/wishbone_manager.prot +++ b/protocols/tests/wishbone_manager/wishbone_manager.prot @@ -26,3 +26,28 @@ struct WBManager { in i_wb_err: u1, in i_wb_data: u32 } + + +prot reset() { + DUT.i_reset := 1'b1; + + // Manager sets `cyc` & `stb` both to 0 + assert_eq(DUT.o_wb_cyc, 1'b0); + assert_eq(DUT.o_wb_stb, 1'b0); + + // Reset means manager is no longer busy + assert_eq(DUT.o_cmd_busy, 1'b0); + + // `o_rsp_stb` is 0 since the output doesn't reference a codeword + assert_eq(DUT.o_rsp_stb, 1'b0); + + step(); +} + +#[idle] +prot idle() { + DUT.i_cmd_stb = 1'b1; + assert_eq(DUT.o_cmd_busy, 1'b0); + step(); +} + From 72e88f9c899be148713aaf2677c8847addfb16f6 Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Thu, 26 Feb 2026 20:51:57 -0500 Subject: [PATCH 5/7] first stab at adding protocols (doesn't work due to type errors for now) --- .../wishbone_manager/wishbone_manager.prot | 150 +++++++++++++++++- .../wishbone_manager/wishbone_manager.tx | 23 +++ 2 files changed, 165 insertions(+), 8 deletions(-) diff --git a/protocols/tests/wishbone_manager/wishbone_manager.prot b/protocols/tests/wishbone_manager/wishbone_manager.prot index 4ad0c41c..91267175 100644 --- a/protocols/tests/wishbone_manager/wishbone_manager.prot +++ b/protocols/tests/wishbone_manager/wishbone_manager.prot @@ -1,6 +1,6 @@ // A Wishbone manager which takes commands encoded via an input 34-bit word, -// issues those commands across a Wishbone bus, -// and returns the result from the bus to the subordinate +// issues those commands across a Wishbone bus, +// and returns the result from the bus to the subordinate struct WBManager { in i_reset: u1, @@ -24,14 +24,13 @@ struct WBManager { in i_wb_stall: u1, in i_wb_ack: u1, in i_wb_err: u1, - in i_wb_data: u32 + in i_wb_data: u32, } - -prot reset() { +prot reset() { DUT.i_reset := 1'b1; - // Manager sets `cyc` & `stb` both to 0 + // Manager sets `cyc` & `stb` both to 0 assert_eq(DUT.o_wb_cyc, 1'b0); assert_eq(DUT.o_wb_stb, 1'b0); @@ -44,10 +43,145 @@ prot reset() { step(); } + #[idle] -prot idle() { - DUT.i_cmd_stb = 1'b1; +prot idle() { + DUT.i_cmd_stb := 1'b1; + assert_eq(DUT.o_cmd_busy, 1'b0); + step(); +} + +// Sets the Wishbone bus address to `addr`. +// Sends a CMD_SUB_ADDR command where the "opcode" is i_cmd_word[33:32] = 2'b10: +// i_cmd_word[31:2] = addr (30-bit absolute address) +// i_cmd_word[1] = 0 +// i_cmd_word[0] = 0 +// +// The manager echoes the new address back on the response channel one cycle +// after the command is accepted. Specifically, after one cycle, +// `o_rsp_stb` is set to 1 and `o_rsp_word` contains the address. +// +// response format in `o_rsp_word`: { 2'b10, addr[29:0], 1'b0, !inc } +// where: +// - RSP_SUB_ADDR = 2'b10 is the "opcode" for the response +// - `inc` is an internal variable in the Verilog module. +// When i_cmd_word[0]=0, inc=1, so !inc=0, i.e. the response is {2'b10, addr, 2'b00}. +prot set_address(in addr: u30) { + DUT.i_reset := 1'b0; + DUT.i_cmd_stb := 1'b1; + + // bit[33:32]=2'b10 (CMD_SUB_ADDR), bits[31:2]=addr, bit[1]=0 (absolute), bit[0]=0 (auto-increment) + DUT.i_cmd_word := 30'b10 + addr + 30'b0; + DUT.i_wb_stall := 1'b0; + DUT.i_wb_ack := 1'b0; + DUT.i_wb_err := 1'b0; + assert_eq(DUT.o_cmd_busy, 1'b0); + step(); + + // At this point, in the Verilog, `o_wb_addr` and `newaddr` are updated. + // TODO: figure out how to express address increment since our langauge + // doesn't have addition + DUT.i_cmd_stb := 1'b0; + step(); + + // Manager echoes the new address back on the response channel + assert_eq(DUT.o_rsp_stb, 1'b1); + assert_eq(DUT.o_rsp_word, 30'b10 + addr + 30'b0); + step(); +} + +// Writes a 32-bit `data` word to the Wishbone bus at the current address. +// Sends the CMD_SUB_WR "opcode" (i_cmd_word[33:32] = 2'b01) with `data` in bits [31:0]. +// This assumes the subordinate does not stall (i_wb_stall = 0 throughout). +// +// State machine progression: +// IDLE -> issue command -> step +// BUS REQUEST (cyc=1, stb=1, we=1): subordinate accepts immediately (stall=0) -> step +// BUS WAIT (cyc=1, stb=0): subordinate acknowledges (ack=1) -> step +// IDLE (cyc=0): RSP_WRITE_ACKNOWLEDGEMENT = {2'b01, 32'x0} on response channel +prot write(in data: u32) { + // IDLE: issue write command + DUT.i_reset := 1'b0; + DUT.i_cmd_stb := 1'b1; + DUT.i_cmd_word := 32'x1 + data; + DUT.i_wb_stall := 1'b0; + DUT.i_wb_ack := 1'b0; + DUT.i_wb_err := 1'b0; + assert_eq(DUT.o_cmd_busy, 1'b0); + step(); + + // BUS REQUEST: manager asserts cyc=1, stb=1, we=1 + DUT.i_cmd_stb := 1'b0; + assert_eq(DUT.o_wb_cyc, 1'b1); + assert_eq(DUT.o_wb_stb, 1'b1); + assert_eq(DUT.o_wb_we, 1'b1); + assert_eq(DUT.o_cmd_busy, 1'b1); + // stall=0, so manager will drop `o_wb_stb` after this step + step(); + + // BUS WAIT: stb has dropped (request accepted), cyc still high + assert_eq(DUT.o_wb_cyc, 1'b1); + assert_eq(DUT.o_wb_stb, 1'b0); + // Subordinate acknowledges the write + DUT.i_wb_ack := 1'b1; + step(); + + // IDLE: cyc dropped, write acknowledged on response channel + assert_eq(DUT.o_wb_cyc, 1'b0); assert_eq(DUT.o_cmd_busy, 1'b0); + assert_eq(DUT.o_rsp_stb, 1'b1); + + // RSP_WRITE_ACKNOWLEDGEMENT = { RSP_SUB_ACK(2'b01), 32'x0 } + assert_eq(DUT.o_rsp_word, 32'x1 + 32'x0); + DUT.i_wb_ack := 1'b0; step(); } +// Reads a 32-bit word from the Wishbone bus at the current address. +// `rdata` is the value provided by the subordinate (driven on i_wb_data). +// Sends CMD_SUB_RD (i_cmd_word[33:32] = 2'b00). +// Assumes the subordinate does not stall (i_wb_stall = 0 throughout). +// +// State progression: +// IDLE -> issue command -> step +// BUS REQUEST (cyc=1, stb=1, we=0): subordinate accepts immediately (stall=0) -> step +// BUS WAIT (cyc=1, stb=0): subordinate acknowledges with data (ack=1) -> step +// IDLE (cyc=0): { RSP_SUB_DATA(2'b00), rdata } on response channel +prot read(in rdata: u32) { + // IDLE: issue read command (lower 32 bits are ignored by the manager) + DUT.i_reset := 1'b0; + DUT.i_cmd_stb := 1'b1; + DUT.i_cmd_word := 34'x0; + DUT.i_wb_stall := 1'b0; + DUT.i_wb_ack := 1'b0; + DUT.i_wb_err := 1'b0; + DUT.i_wb_data := rdata; + assert_eq(DUT.o_cmd_busy, 1'b0); + step(); + + // BUS REQUEST: manager asserts cyc=1, stb=1, we=0 + DUT.i_cmd_stb := 1'b0; + assert_eq(DUT.o_wb_cyc, 1'b1); + assert_eq(DUT.o_wb_stb, 1'b1); + assert_eq(DUT.o_wb_we, 1'b0); + assert_eq(DUT.o_cmd_busy, 1'b1); + // stall=0, so manager will drop stb after this step + step(); + + // BUS WAIT: stb has dropped (request accepted), cyc still high + assert_eq(DUT.o_wb_cyc, 1'b1); + assert_eq(DUT.o_wb_stb, 1'b0); + // Subordinate acknowledges with read data + DUT.i_wb_ack := 1'b1; + DUT.i_wb_data := rdata; + step(); + + // IDLE: cyc dropped, read data on response channel + assert_eq(DUT.o_wb_cyc, 1'b0); + assert_eq(DUT.o_cmd_busy, 1'b0); + assert_eq(DUT.o_rsp_stb, 1'b1); + // { RSP_SUB_DATA(2'b00), rdata } + assert_eq(DUT.o_rsp_word, 32'x0 + rdata); + DUT.i_wb_ack := 1'b0; + step(); +} diff --git a/protocols/tests/wishbone_manager/wishbone_manager.tx b/protocols/tests/wishbone_manager/wishbone_manager.tx index e69de29b..ddb3805b 100644 --- a/protocols/tests/wishbone_manager/wishbone_manager.tx +++ b/protocols/tests/wishbone_manager/wishbone_manager.tx @@ -0,0 +1,23 @@ +// ARGS: --verilog wishbone_manager/hbexec.v --protocol=wishbone_manager/wishbone_manager.prot --module hbexec +trace { + reset(); + + // Set the bus address to 0x10 (absolute, auto-increment enabled) + set_address(30'h10); + + // Write 0xDEADBEEF to address 0x10. + // Address auto-increments to 0x11 after the request is accepted. + // TODO: need to figure out how to increment the address since our + // language doesn't have addition + write(0xDEADBEEF); + + // The subordinate returns 0xCAFEF00D from address 0x11. + // Manager should echo it as { 2'b00, 0xCAFEF00D } in o_rsp_word. + // Address auto-increments to 0x12. + read(0xCAFEF00D); + + // Write 0xABCD1234 to address 0x12. + write(0xABCD1234); + + idle(); +} From 23b773d754b62ca5fa255f7509bcba592ffdb69b Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Mon, 2 Mar 2026 10:35:37 -0500 Subject: [PATCH 6/7] Refactor read & write protocols to take in extra address arg --- protocols/tests/wishbone_manager/hbexec.v | 2 +- .../wishbone_manager/wishbone_manager.prot | 140 +++++++++--------- .../wishbone_manager/wishbone_manager.tx | 20 +-- 3 files changed, 73 insertions(+), 89 deletions(-) diff --git a/protocols/tests/wishbone_manager/hbexec.v b/protocols/tests/wishbone_manager/hbexec.v index c7937ece..11fed771 100644 --- a/protocols/tests/wishbone_manager/hbexec.v +++ b/protocols/tests/wishbone_manager/hbexec.v @@ -60,7 +60,7 @@ `define CMD_SUB_RD 2'b00 // Read request `define CMD_SUB_WR 2'b01 // Write request -`define CMD_SUB_BUS 1'b0 // A request in general +`define CMD_SUB_BUS 1'b0 // Bus request (either a read or a write) `define CMD_SUB_ADDR 2'b10 // Set an address `define CMD_SUB_SPECIAL 2'b11 // Bus reset diff --git a/protocols/tests/wishbone_manager/wishbone_manager.prot b/protocols/tests/wishbone_manager/wishbone_manager.prot index 91267175..d2ed59f5 100644 --- a/protocols/tests/wishbone_manager/wishbone_manager.prot +++ b/protocols/tests/wishbone_manager/wishbone_manager.prot @@ -27,6 +27,7 @@ struct WBManager { in i_wb_data: u32, } + prot reset() { DUT.i_reset := 1'b1; @@ -51,75 +52,55 @@ prot idle() { step(); } -// Sets the Wishbone bus address to `addr`. -// Sends a CMD_SUB_ADDR command where the "opcode" is i_cmd_word[33:32] = 2'b10: -// i_cmd_word[31:2] = addr (30-bit absolute address) -// i_cmd_word[1] = 0 -// i_cmd_word[0] = 0 -// -// The manager echoes the new address back on the response channel one cycle -// after the command is accepted. Specifically, after one cycle, -// `o_rsp_stb` is set to 1 and `o_rsp_word` contains the address. -// -// response format in `o_rsp_word`: { 2'b10, addr[29:0], 1'b0, !inc } -// where: -// - RSP_SUB_ADDR = 2'b10 is the "opcode" for the response -// - `inc` is an internal variable in the Verilog module. -// When i_cmd_word[0]=0, inc=1, so !inc=0, i.e. the response is {2'b10, addr, 2'b00}. -prot set_address(in addr: u30) { +// This protocol writes a 32-bit `data` word to the Wishbone bus at `addr`, +// assuming that the subordinate does not stall (i_wb_stall = 0 throughout). +// Under the hood, this corresponds to a "set address" command (`CMD_SUB_ADDR`), +// followed immediately by a "write" command (`CMD_SUB_WR`). +// (The two commands can be issued back to back, since updating the address +// only updates the address and doesn't affect `cyc` or `stb`, and the latter +// is what governs when `read` / `write` protocols can fire.) +// State progression: +// 1. IDLE: send CMD_SUB_ADDR, step -> address updated +// 2. IDLE: send CMD_SUB_WR, step -> cyc=1, stb=1, we=1 (bus request starts) +// 3. BUS REQUEST (cyc=1, stb=1, we=1): subordinate accepts (stall=0), step +// 4. BUS WAIT (cyc=1, stb=0): subordinate acknowledges (ack=1), step +// 5. IDLE (cyc=0): RSP_WRITE_ACKNOWLEDGEMENT = {2'b01, 32'x0} on response channel +prot write(in addr: u30, in data: u32) { + // Issue a "set address" command (`CMD_SUB_ADDR`) + // In `i_cmd_word`, we have: + // - bits[33:32] = 2'b10 (opcode for `CMD_SUB_ADDR`) + // - bits[31:2] = the actual address DUT.i_reset := 1'b0; DUT.i_cmd_stb := 1'b1; - - // bit[33:32]=2'b10 (CMD_SUB_ADDR), bits[31:2]=addr, bit[1]=0 (absolute), bit[0]=0 (auto-increment) DUT.i_cmd_word := 30'b10 + addr + 30'b0; DUT.i_wb_stall := 1'b0; DUT.i_wb_ack := 1'b0; DUT.i_wb_err := 1'b0; assert_eq(DUT.o_cmd_busy, 1'b0); - step(); + step(); // Address is updated in the Verilog during this step - // At this point, in the Verilog, `o_wb_addr` and `newaddr` are updated. - // TODO: figure out how to express address increment since our langauge - // doesn't have addition - DUT.i_cmd_stb := 1'b0; - step(); - - // Manager echoes the new address back on the response channel - assert_eq(DUT.o_rsp_stb, 1'b1); - assert_eq(DUT.o_rsp_word, 30'b10 + addr + 30'b0); - step(); -} - -// Writes a 32-bit `data` word to the Wishbone bus at the current address. -// Sends the CMD_SUB_WR "opcode" (i_cmd_word[33:32] = 2'b01) with `data` in bits [31:0]. -// This assumes the subordinate does not stall (i_wb_stall = 0 throughout). -// -// State machine progression: -// IDLE -> issue command -> step -// BUS REQUEST (cyc=1, stb=1, we=1): subordinate accepts immediately (stall=0) -> step -// BUS WAIT (cyc=1, stb=0): subordinate acknowledges (ack=1) -> step -// IDLE (cyc=0): RSP_WRITE_ACKNOWLEDGEMENT = {2'b01, 32'x0} on response channel -prot write(in data: u32) { - // IDLE: issue write command - DUT.i_reset := 1'b0; + // Issue a write command immediately by setting `stb = 1` + // (`o_cmd_busy = 0` since `cyc = 0`) + // In `i_cmd_word`, we have: + // - bits[33:32] = 2'b01 (opcode for `CMD_SUB_WR`) + // - bits[31:0] = data DUT.i_cmd_stb := 1'b1; DUT.i_cmd_word := 32'x1 + data; - DUT.i_wb_stall := 1'b0; - DUT.i_wb_ack := 1'b0; - DUT.i_wb_err := 1'b0; assert_eq(DUT.o_cmd_busy, 1'b0); - step(); + assert_eq(DUT.o_wb_addr, addr); + step(); // cyc=1, stb=1, we=1; address echo arrives on rsp channel simultaneously - // BUS REQUEST: manager asserts cyc=1, stb=1, we=1 + // BUS REQUEST: up to now, the manager has asserted cyc=1, stb=1, we=1 + // The manager now sets `stb = 0` (since the subordinate doesn't stall) DUT.i_cmd_stb := 1'b0; - assert_eq(DUT.o_wb_cyc, 1'b1); - assert_eq(DUT.o_wb_stb, 1'b1); - assert_eq(DUT.o_wb_we, 1'b1); + assert_eq(DUT.o_wb_cyc, 1'b1); + assert_eq(DUT.o_wb_stb, 1'b1); + assert_eq(DUT.o_wb_we, 1'b1); assert_eq(DUT.o_cmd_busy, 1'b1); - // stall=0, so manager will drop `o_wb_stb` after this step step(); - // BUS WAIT: stb has dropped (request accepted), cyc still high + // BUS WAIT: We now have `stb = 0` (the request has been accepted) + // `cyc` is still 1 since we're still in the same session assert_eq(DUT.o_wb_cyc, 1'b1); assert_eq(DUT.o_wb_stb, 1'b0); // Subordinate acknowledges the write @@ -127,48 +108,58 @@ prot write(in data: u32) { step(); // IDLE: cyc dropped, write acknowledged on response channel - assert_eq(DUT.o_wb_cyc, 1'b0); + assert_eq(DUT.o_wb_cyc, 1'b0); assert_eq(DUT.o_cmd_busy, 1'b0); - assert_eq(DUT.o_rsp_stb, 1'b1); - + assert_eq(DUT.o_rsp_stb, 1'b1); // RSP_WRITE_ACKNOWLEDGEMENT = { RSP_SUB_ACK(2'b01), 32'x0 } assert_eq(DUT.o_rsp_word, 32'x1 + 32'x0); DUT.i_wb_ack := 1'b0; step(); } -// Reads a 32-bit word from the Wishbone bus at the current address. -// `rdata` is the value provided by the subordinate (driven on i_wb_data). -// Sends CMD_SUB_RD (i_cmd_word[33:32] = 2'b00). -// Assumes the subordinate does not stall (i_wb_stall = 0 throughout). -// +// This protocol reads a 32-bit word from the Wishbone bus at `addr`, +// where `rdata` is the value provided by the subordinate (driven on i_wb_data) +// (this is why `rdata` is an input parameter). +// Like the `write` protocol above, we issue two commands back to back, +// first by setting the address via `CMD_SUB_ADDR` +// then by performing a read (`CMD_SUB_RD`)// // State progression: -// IDLE -> issue command -> step -// BUS REQUEST (cyc=1, stb=1, we=0): subordinate accepts immediately (stall=0) -> step -// BUS WAIT (cyc=1, stb=0): subordinate acknowledges with data (ack=1) -> step -// IDLE (cyc=0): { RSP_SUB_DATA(2'b00), rdata } on response channel -prot read(in rdata: u32) { - // IDLE: issue read command (lower 32 bits are ignored by the manager) +// 1. IDLE: send CMD_SUB_ADDR, step -> addr latched, newaddr=1 +// 2. IDLE: send CMD_SUB_RD, step -> cyc=1, stb=1, we=0 (bus cycle starts) +// 3. BUS REQUEST (cyc=1, stb=1, we=0): subordinate accepts (stall=0), step +// 4. BUS WAIT (cyc=1, stb=0): subordinate acknowledges with data (ack=1), step +// 5. IDLE (cyc=0): Send response +prot read(in addr: u30, in rdata: u32) { + // Set the bus address DUT.i_reset := 1'b0; DUT.i_cmd_stb := 1'b1; - DUT.i_cmd_word := 34'x0; + DUT.i_cmd_word := 30'b10 + addr + 30'b0; DUT.i_wb_stall := 1'b0; DUT.i_wb_ack := 1'b0; DUT.i_wb_err := 1'b0; DUT.i_wb_data := rdata; assert_eq(DUT.o_cmd_busy, 1'b0); - step(); + step(); + + // Issue read command immediately + // In `i_cmd_word`, we have `bits[33:32]=2'b00 (opcode for CMD_SUB_RD)` + // and the lower 32 bits are ignored by the manager (i.e. all zeroes) + DUT.i_cmd_stb := 1'b1; + DUT.i_cmd_word := 34'x0; + assert_eq(DUT.o_cmd_busy, 1'b0); + assert_eq(DUT.o_wb_addr, addr); + step(); - // BUS REQUEST: manager asserts cyc=1, stb=1, we=0 + // BUS REQUEST: manager DUT has asserted cyc=1, stb=1, we=0 DUT.i_cmd_stb := 1'b0; assert_eq(DUT.o_wb_cyc, 1'b1); assert_eq(DUT.o_wb_stb, 1'b1); assert_eq(DUT.o_wb_we, 1'b0); assert_eq(DUT.o_cmd_busy, 1'b1); - // stall=0, so manager will drop stb after this step + // We currently have stall=0, so the manager sets `stb = 0` after this step step(); - // BUS WAIT: stb has dropped (request accepted), cyc still high + // BUS WAIT: The manager sets `stb = 0` (request accepted), cyc still high assert_eq(DUT.o_wb_cyc, 1'b1); assert_eq(DUT.o_wb_stb, 1'b0); // Subordinate acknowledges with read data @@ -176,11 +167,12 @@ prot read(in rdata: u32) { DUT.i_wb_data := rdata; step(); - // IDLE: cyc dropped, read data on response channel + // IDLE: `cyc` becomes 0 (to indicate the end of a session) + // The read data is conveyed via the response assert_eq(DUT.o_wb_cyc, 1'b0); assert_eq(DUT.o_cmd_busy, 1'b0); assert_eq(DUT.o_rsp_stb, 1'b1); - // { RSP_SUB_DATA(2'b00), rdata } + // response format is { 2'b00 (opcode for RSP_SUB_DATA), rdata } assert_eq(DUT.o_rsp_word, 32'x0 + rdata); DUT.i_wb_ack := 1'b0; step(); diff --git a/protocols/tests/wishbone_manager/wishbone_manager.tx b/protocols/tests/wishbone_manager/wishbone_manager.tx index ddb3805b..b33bbed2 100644 --- a/protocols/tests/wishbone_manager/wishbone_manager.tx +++ b/protocols/tests/wishbone_manager/wishbone_manager.tx @@ -2,22 +2,14 @@ trace { reset(); - // Set the bus address to 0x10 (absolute, auto-increment enabled) - set_address(30'h10); + // Write 0xDEADBEEF to address 0x10 + write(16, 32'xDEADBEEF); - // Write 0xDEADBEEF to address 0x10. - // Address auto-increments to 0x11 after the request is accepted. - // TODO: need to figure out how to increment the address since our - // language doesn't have addition - write(0xDEADBEEF); + // Read from address 0x11; subordinate returns 0xCAFEF00D + read(17, 32'xCAFEF00D); - // The subordinate returns 0xCAFEF00D from address 0x11. - // Manager should echo it as { 2'b00, 0xCAFEF00D } in o_rsp_word. - // Address auto-increments to 0x12. - read(0xCAFEF00D); - - // Write 0xABCD1234 to address 0x12. - write(0xABCD1234); + // Write 0xABCD1234 to address 0x12 + write(18, 32'xABCD1234); idle(); } From 33bb762f15ab2959c82f3743509d183a7a3ff47a Mon Sep 17 00:00:00 2001 From: Ernest Ng Date: Mon, 2 Mar 2026 10:50:16 -0500 Subject: [PATCH 7/7] Fix issues with reset and idle protocols --- .../wishbone_manager/wishbone_manager.prot | 19 +++++++++++++++---- .../wishbone_manager/wishbone_manager.tx | 12 ++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/protocols/tests/wishbone_manager/wishbone_manager.prot b/protocols/tests/wishbone_manager/wishbone_manager.prot index d2ed59f5..43c26588 100644 --- a/protocols/tests/wishbone_manager/wishbone_manager.prot +++ b/protocols/tests/wishbone_manager/wishbone_manager.prot @@ -31,6 +31,12 @@ struct WBManager { prot reset() { DUT.i_reset := 1'b1; + // Set other inputs to DontCare + DUT.i_cmd_stb := X; + DUT.i_wb_ack := X; + DUT.i_wb_stall := X; + DUT.i_wb_err := X; + // Manager sets `cyc` & `stb` both to 0 assert_eq(DUT.o_wb_cyc, 1'b0); assert_eq(DUT.o_wb_stb, 1'b0); @@ -38,16 +44,21 @@ prot reset() { // Reset means manager is no longer busy assert_eq(DUT.o_cmd_busy, 1'b0); - // `o_rsp_stb` is 0 since the output doesn't reference a codeword - assert_eq(DUT.o_rsp_stb, 1'b0); - step(); + + // After reset, the DUT sets o_rsp_word to `RSP_RESET` + // & sets `o_rsp_stb = 1` (since the output contains a valid command word). + // The format of `RSP_RESET` = { 2'b11 (opcode for RSP_SUB_SPECIAL), 32'h0 }, + // where `RSP_SUB_SPECIAL` is the response for a reset + assert_eq(DUT.o_rsp_stb, 1'b1); + assert_eq(DUT.o_rsp_word, 32'x3 + 32'x0); } #[idle] prot idle() { - DUT.i_cmd_stb := 1'b1; + // Set `stb = 0`, since no command is currently being issued + DUT.i_cmd_stb := 1'b0; assert_eq(DUT.o_cmd_busy, 1'b0); step(); } diff --git a/protocols/tests/wishbone_manager/wishbone_manager.tx b/protocols/tests/wishbone_manager/wishbone_manager.tx index b33bbed2..def69211 100644 --- a/protocols/tests/wishbone_manager/wishbone_manager.tx +++ b/protocols/tests/wishbone_manager/wishbone_manager.tx @@ -2,14 +2,14 @@ trace { reset(); - // Write 0xDEADBEEF to address 0x10 - write(16, 32'xDEADBEEF); + // Write 0xDEADBEEF to address 16 = 0x10 + write(16, 0xDEADBEEF); - // Read from address 0x11; subordinate returns 0xCAFEF00D - read(17, 32'xCAFEF00D); + // Read from address 17 = 0x11; subordinate returns 0xCAFEF00D + read(17, 0xCAFEF00D); - // Write 0xABCD1234 to address 0x12 - write(18, 32'xABCD1234); + // Write 0xABCD1234 to address 18 = 0x12 + write(18, 0xABCD1234); idle(); }