Created
August 10, 2017 19:54
-
-
Save kierdavis/b236e9b5281986cb24fce1d7516edd4c to your computer and use it in GitHub Desktop.
Example of literate SystemVerilog (using noweb)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
\chapter{Introduction} | |
This document explains the reference SystemVerilog implementation of the charon architecture. | |
\chapter{Core} | |
This chapter covers the implementation of the core architecture as defined by \todo{reference to architecture spec}. That is, the intrastructure required to transfer data words between abstract ports as dictated by a sequence of instructions in main memory. It does not assign any meaning to the ports. | |
\section{Top-level structure} | |
The top-level module is named [[charon]]. Its structure is typical of SystemVerilog source code, consisting of some port declarations, some type declarations, some node declarations, some continuous assignments and some [[always]]/[[initial]] behavioural blocks. | |
<<*>>= | |
module charon( | |
<<ports>> | |
); | |
<<types>> | |
<<nodes>> | |
<<assignments>> | |
<<blocks>> | |
endmodule | |
@ | |
\subsection{Datatypes} | |
\todo[inline]{Explain this bit} | |
<<types>>= | |
typedef logic [15:0] Word; | |
@ | |
<<types>>= | |
typedef Word Address; | |
@ | |
\subsection{Registers} | |
The system will of course contain a number of registers, controlled by global [[clock]] and [[reset_n]] signals. The suffix [[_n]] denotes an active-low signal. A standard behaviour block infers the registers. | |
<<ports>>= | |
input logic clock, | |
input logic reset_n, | |
@ | |
<<blocks>>= | |
// Sequential behaviour for all registers. | |
always_ff @(posedge clock, negedge reset_n) begin | |
if (~reset_n) begin | |
<<reset_regs>> | |
end | |
else begin | |
<<update_regs>> | |
end | |
end | |
@ | |
\section{State machine} | |
A number of steps are required in order to process each instruction in the program. These are (roughly): | |
\begin{itemize} | |
\item \emph{Fetch}: transfer instruction word from memory into the [[instruction]]\todo{link} register. | |
\item \emph{Read}: Transfer the data word from the source specified by the instruction into the [[xfer_data]]\todo{link} register. | |
\item \emph{Write}: Transfer the contents of the [[xfer_data]] register to the destination specified by the instruction. | |
\end{itemize} | |
All three of these steps may involve a memory access, and the implementation does not support performing more than one memory access in a single clock cycle. Therefore, we will implement a finite state machine to control the behaviour of the machine across multiple clock cycles. | |
\subsection{Structure} | |
We start by defining the framework for an enumeration of possible states; the names of the states themselves will be filled in as we walk through the fetch-read-write cycle. | |
<<types>>= | |
typedef enum { | |
<<states>> | |
} State; | |
@ | |
The current state is maintained in a register, surprisingly named [[state]]. The value to be stored into [[state]] on each clock edge is given by the combinational node [[state_next]], itself an output of the state machine's combinational logic block. | |
<<nodes>>= | |
State state; // Register holding the current state. | |
State state_next; // Value to be loaded into `state` on the next clock cycle. | |
@ | |
<<reset_regs>>= | |
state <= <<start_state>>; | |
@ | |
<<update_regs>>= | |
state <= state_next; | |
@ | |
We will also create a combinational logic block that derives the values to be stored into the machine's registers (including the next state) from the current values of the machine's registers as well as any additional inputs. | |
<<blocks>>= | |
// Main state machine output logic / next state logic. | |
always_comb begin | |
// Ensure all outputs of this logic block are set to a default value before we enter the decision-making section. | |
<<reset_fsm_outputs>> | |
// Decide what to do based on the current state. | |
case (state) | |
<<per_state_logic>> | |
endcase | |
end | |
@ | |
[[state_next]] is an output of this logic block, so we should make sure that it is assigned to a default value at the top of the logic block. A sensible default for this node would be an "error state". This state \emph{should} never be entered, so if the system does end up in this state then it is indicative of a flaw in the logic block. | |
<<states>>= | |
STATE_ERROR, | |
@ | |
<<reset_fsm_outputs>>= | |
state_next = STATE_ERROR; // Unless otherwise specified, assume a flow control error has occurred. | |
@ | |
\subsection{Fetch phase} | |
The first phase in the instruction fetch-execute cycle is to load the instruction from memory into the [[instruction]]\todo{link} register. In the case of instructions that are two machine words long (32 bits), we will only load the first word here; the second word will be loaded later, in the read phase. | |
We start by adding an entry to the [[State]] enumeration to represent this state of execution. | |
<<states>>= | |
STATE_FETCH, | |
@ | |
<<start_state>>= | |
STATE_FETCH | |
@ | |
<<per_state_logic>>= | |
STATE_FETCH: begin | |
<<fetch_logic>> | |
end | |
@ | |
When the system is in the fetch state, the memory subsystem\todo{link} should be instructed to load a word from memory at the address given by the program counter ([[pc]])\todo{link} register. | |
<<fetch_logic>>= | |
// Load a word at address `pc`. | |
vmem_read = 1'h1; | |
vmem_address = pc; | |
@ | |
The memory subsystem may hold the [[vmem_stall]] node high for some number of clock cycles until data is ready on the [[data_bus]]\todo{link}. Once data does become ready, we want it to be stored into the [[instruction]] register. | |
<<fetch_logic>>= | |
if (vmem_stall) begin | |
// Data not ready; stay in the same state. | |
state_next = STATE_FETCH; | |
end | |
else begin | |
// Data is ready; store it into `instruction`. | |
instruction_next = data_bus; | |
<<fetch_logic_final>> | |
end | |
@ | |
The instruction word is now ready to be stored into [[instruction]] on the next clock edge. Two other things also need to happen on this clock edge: the program counter needs to be incremented, and the system needs to move to the read state. | |
<<fetch_logic_final>>= | |
// Increment program counter. | |
pc_next = pc + 16'h1; | |
// Go to read state. | |
state_next = STATE_READ; | |
@ | |
\subsection{Read phase} | |
Now that the first instruction word is stored in its corresponding register, the next step is obtain the data word to be transferred by the instruction and store it into the [[xfer_data]]\todo{link} register. As before, we will define a new FSM state named [[STATE_READ]]. | |
<<states>>= | |
STATE_READ, | |
@ | |
<<per_state_logic>>= | |
STATE_READ: begin | |
<<read_logic>> | |
end | |
@ | |
Recall from the architecture specification that there are three ways in which we can obtain the data word, as determined by bits 7 and 15 of the instruction word. | |
<<read_logic>>= | |
if (instruction[15]) begin | |
if (instruction[7]) begin | |
// Source is a port, with port number given by instruction[6:0]. | |
<<read_port_logic>> | |
end | |
else begin | |
// Source is a 16-bit literal stored in the second word of this two-word instruction. | |
<<read_lit16_logic>> | |
end | |
end | |
else begin | |
// Source is an 8-bit literal stored in instruction[7:0]. | |
<<read_lit8_logic>> | |
end | |
@ | |
The simplest of these the 8-bit literal, as the data is already stored in the [[instruction]] register. It simply needs to be sign-extended to 16 bits and transferred to the [[xfer_data]] register ready for use by the write phase. | |
<<read_lit8_logic>>= | |
xfer_data_next = {{8{instruction[7]}}, instruction[7:0]}; | |
state_next = STATE_WRITE; | |
@ | |
The 16-bit literal read is achieved in almost exactly the same manner as in the fetch phase. | |
<<read_lit16_logic>>= | |
// Load a word at address `pc`. | |
vmem_read = 1'h1; | |
vmem_address = pc; | |
if (vmem_stall) begin | |
// Data not ready; stay in the same state. | |
state_next = STATE_READ; | |
end | |
else begin | |
// Data is ready; store it into `data`. | |
xfer_data_next = data_bus; | |
// Increment program counter. | |
pc_next = pc + 16'h1; | |
// Go to write state. | |
state_next = STATE_WRITE; | |
end | |
@ | |
Since the functionality of ports is not part of the core specification, the definition of the logic to read from them will be postponed until a later section.\todo{link} | |
\subsection{Write phase} | |
The write phase transfers the word that was previously stored to [[xfer_data]] to the port specified by the instruction. As before, we will define a new FSM state named [[STATE_WRITE]]. | |
<<states>>= | |
STATE_WRITE, | |
@ | |
<<per_state_logic>>= | |
STATE_WRITE: begin | |
<<write_logic>> | |
end | |
@ | |
There are fewer decisions to make here than in the read state, since the only destination that one can write to is a port. | |
<<write_logic>>= | |
<<write_port_logic>> | |
@ | |
As mentioned in the previous section, the definition of the logic to write to ports will be postponed until a later section.\todo{link} | |
\section{Auxillary registers} | |
The core state machine logic outlined in section\todo{link} referred to a number of registers, which will now be defined. | |
\subsection{Instruction register} | |
The [[instruction]] register holds the first machine word of the instruction currently being processed. It is 16 bits wide. | |
<<nodes>>= | |
Word instruction; // Register holding the current instruction. | |
Word instruction_next; // Value to be loaded into `instruction` on the next clock cycle. | |
@ | |
<<reset_regs>>= | |
instruction <= 16'h0; | |
@ | |
<<update_regs>>= | |
instruction <= instruction_next; | |
@ | |
<<reset_fsm_outputs>>= | |
instruction_next = instruction; // Unless otherwise specified, retain the same value. | |
@ | |
\subsection{Program counter register} | |
The [[pc]] register holds the address of the next program word to be fetched from memory. Like all addresses, it is 16 bits wide. | |
<<nodes>>= | |
Address pc; // Register holding the address of the next program word. | |
Address pc_next; // Value to be loaded into `pc` on the next clock cycle. | |
@ | |
<<reset_regs>>= | |
pc <= 16'h0; | |
@ | |
<<update_regs>>= | |
pc <= pc_next; | |
@ | |
<<reset_fsm_outputs>>= | |
pc_next = pc; // Unless otherwise specified, retain the same value. | |
@ | |
\subsection{Data transfer register} | |
The [[xfer_data]] register holds the data word that the instruction describes the transfer of. Its value is retrieved from a particular source during the read phase, and is then stored to a particular destination during the write phase. It is 16 bits wide. | |
<<nodes>>= | |
Word xfer_data; // Register holding the data word transferred by the instruction. | |
Word xfer_data_next; // Value to be loaded into `xfer_data` on the next clock cycle. | |
@ | |
<<reset_regs>>= | |
xfer_data <= 16'h0; | |
@ | |
<<update_regs>>= | |
xfer_data <= xfer_data_next; | |
@ | |
<<reset_fsm_outputs>>= | |
xfer_data_next = xfer_data; // Unless otherwise specified, retain the same value. | |
@ | |
\section{Memory interface} | |
The core state machine logic outlined in section\todo{link} also referred to the memory subsystem, which will now be defined. | |
The [[charon]] module does not include the actual memory storage, instead providing an interface that can be used to attach a number of external memory devices. The interface is defined as follows: | |
<<ports>>= | |
output logic vmem_read, | |
output logic vmem_write, | |
input logic vmem_stall, | |
output Address vmem_address, | |
@ | |
To initiate a memory operation, the [[charon]] module will assert either the [[vmem_read]] or [[vmem_write]] signal while providing a valid address on [[vmem_address]]. At each following clock edge, the [[charon]] module will check the status of the [[vmem_stall]] signal. If it is high, it continues to provide the same values on [[vmem_read]], [[vmem_write]] and [[vmem_address]]. If it is low, then it takes this to mean that the memory operation is complete, and in the case that the operation is a memory read, the data is available on [[data_bus]] to be stored into a register in the [[charon]] module on the same clock edge. | |
[[vmem_read]], [[vmem_write]] and [[vmem_address]] are all outputs of the core state machine, so they need their defaults to be declared. | |
<<reset_fsm_outputs>>= | |
vmem_read = 1'h0; // Unless otherwise specified, don't initiate a memory operation. | |
vmem_write = 1'h0; // Unless otherwise specified, don't initiate a memory operation. | |
vmem_address = 16'h0; // Unless otherwise specified, assume memory address 0. | |
@ | |
These signals are manipulated at the points in the state machine logic block at which memory operations are required. | |
\chapter{Stub} | |
<<read_port_logic>>= | |
// Stub (no ports are implemented) | |
xfer_data_next = 16'hDEAD; | |
state_next = STATE_WRITE; | |
@ | |
<<write_port_logic>>= | |
// Stub (no ports are implemented) | |
state_next = STATE_FETCH; | |
@ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment