Created
July 16, 2016 21:56
-
-
Save sam-falvo/71139ddfc4e9b80c47e3fcce18e1f500 to your computer and use it in GitHub Desktop.
This file contains 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
`timescale 1ns / 1ps | |
// The fetch module is responsible for managing instruction fetch operations. | |
// This implies it must also deal with external interrupts as well as | |
// internally generated traps. | |
module fetch( | |
input reset_i, | |
input clk_i, | |
output [63:0] adr_o, | |
input ack_i, | |
input defined_i, | |
input pause_i, | |
output [1:0] size_o | |
); | |
// The ADR_O bus is used to address external memory. | |
// It's a full, 64-bit bus instead of a 63-bit bus, like what you'd | |
// expect from a Wishbone bus. This actually simplifies the control | |
// logic as well as the external Wishbone bridge. | |
wire [63:0] adr_o; | |
// We multiplex all the different sources of addresses onto this | |
// address bus. Sometimes, we'll need to pad bits to make things | |
// line up right. | |
assign adr_o = | |
(ADR_NPC) ? {r_npc, 2'b00} : | |
(ADR_NPCp2) ? {r_npc, 2'b10} : | |
64'd0; | |
// The instruction fetcher needs to know from where to fetch the next | |
// instruction. This is the purpose of the NPC register. | |
reg [63:2] r_npc; | |
// The SIZE_O bus indicates how big the transfer size is, and | |
// how many data bits of the input data bus are going to be used. | |
// The meanings are: | |
// | |
// 00 No bus cycle in progress. | |
// 01 One byte is expected to appear on DAT_I[7:0] | |
// 10 Two bytes are expected to appear on DAT_I[15:0] | |
// 11 Unused; must never appear. | |
reg [1:0] size_o; | |
// The instruction fetcher is a self-contained state machine. | |
// Some of the bits that determines the current state comes from | |
// a register called state. Others are taken as inputs from other | |
// parts of the computer. At present, there are 13 different states. | |
reg [2:0] state; | |
wire s0 = state_o == 3'b000; | |
wire s1 = state_o == 3'b001; | |
wire s2 = state_o == 3'b010; | |
wire s3 = state_o == 3'b011; | |
wire s4 = state_o == 3'b100; | |
wire s5 = state_o == 3'b101; | |
wire fire0 = (~reset_i) & (~defined_i) & s0; | |
wire fire1 = (~reset_i) & defined_i & pause_i & s0; | |
wire fire2 = (~reset_i) & defined_i & (~pause_i) & s0; | |
wire fire3 = (~reset_i) & (~ack_i) & s1; | |
wire fire4 = (~reset_i) & ack_i & s1; | |
wire fire5 = (~reset_i) & s2; | |
wire fire6 = (~reset_i) & (~ack_i) & s3; | |
wire fire7 = (~reset_i) & (~pause_i) & ack_i & s3; | |
wire fire8 = (~reset_i) & pause_i & ack_i & s3; | |
wire fire9 = (~reset_i) & pause_i & s4; | |
wire fire10 = (~reset_i) & (~pause_i) & s4; | |
wire fire11 = (~reset_i) & pause_i & s5; | |
wire fire12 = (~reset_i) & (~pause_i) & s5; | |
// Depending on which state case is firing at the moment, | |
// we want to set our next state in a sensible way. | |
wire [2:0] next_state = | |
(fire0 | fire2 | fire3) ? 1 : | |
(fire4) ? 2 : | |
(fire5 | fire6) ? 3 : | |
(fire8 | fire9) ? 4 : | |
(fire1 | fire11) ? 5 : | |
0; | |
always @(posedge clk_i or negedge clk_i) begin | |
$display("C%d ST=%d NS=%d F=%12b R%d D%d P%d A%d", clk_i, state, next_state, {fire0, fire1, fire2, fire3, fire4,fire5,fire6,fire7,fire8,fire9,fire10,fire11,fire12}, reset_i, defined_i, pause_i, ack_i); | |
end | |
// We only want to apply the low halfword instruction address when | |
// fetching the low halfword of the next opcode. Similarly, we | |
// only want to ask for the high halfword when we're ready. | |
wire ADR_NPC = fire2 | fire3 | fire4; | |
wire ADR_NPCp2 = |{fire5, fire6, fire7, fire8}; | |
wire is_opcode_fetch = |{fire0, fire2, fire3, fire4, fire5, fire6, fire7, fire8}; | |
wire SIZE_2 = is_opcode_fetch; | |
wire VPA_O = is_opcode_fetch; | |
// If we hit an undefined instruction, we want to take the exception. | |
wire ADR_MTVEC = fire0; | |
wire NPC_MTVEC = fire0; | |
// Whenever we handle an exception, we must set MEPC to either the CPC | |
// or to the NPC, depending on the nature of the exception. | |
wire MEPC_CPC = fire0; | |
// For every exception, we enter machine-mode, and that means turning | |
// off machine-mode interrupts. We also need to preserve the previous | |
// setting. | |
wire MPIE_MIE = fire0; | |
wire MIE_0 = fire0; | |
// Every exception has a cause. This logic figures out what it is. | |
wire MCAUSE_2 = fire0; | |
// We fetch instructions in two halfwords, to accomodate the 16-bit | |
// external bus. | |
reg [15:0] irl; | |
reg [15:0] irh; | |
// We trigger updates to IRL and IRH only at the appropriate times. | |
// Note that we can also optimize out one cycle if we recognize when | |
// we can bypass the loading of IRH. | |
wire IRL_DAT = fire4; | |
wire IRH_DAT = fire7 | fire8; | |
wire IR_DAT_IRL = fire7 | fire10; | |
wire CPC_NPC = IR_DAT_IRL; | |
wire NPC_NPCp4 = CPC_NPC; | |
always @(posedge clk_i) begin | |
if(reset_i) begin | |
// Upon hard reset, | |
// the NPC assumes the value $FFFFFFFFFFFFFF00. | |
r_npc <= 62'h3FFFFFFFFFFFFFC0; | |
// When we're in hard reset, all bus activity must | |
// come to a close. | |
size_o <= 2'b00; | |
end | |
// Upon every clock, we want to advance to the next state. | |
state <= next_state; | |
end | |
endmodule | |
// Exercise the instruction fetch module. | |
module test_fetch(); | |
reg [15:0] story_o; | |
reg ack_o; | |
reg clk_o; | |
reg defined_o; | |
reg pause_o; | |
reg reset_o; | |
wire [1:0] size_i; | |
wire [63:0] adr_i; | |
fetch f( | |
.ack_i(ack_o), | |
.adr_o(adr_i), | |
.clk_i(clk_o), | |
.reset_i(reset_o), | |
.size_o(size_i), | |
.defined_i(defined_o), | |
.pause_i(pause_o) | |
); | |
always begin | |
#20 clk_o <= ~clk_o; | |
end | |
task tick; | |
input [15:0] story; | |
begin | |
story_o <= story; | |
wait(clk_o); wait(~clk_o); | |
end | |
endtask | |
task assert_adr; | |
input [63:0] expected; | |
begin | |
if(adr_i !== expected) begin | |
$display("@E %04X ADR_O Expected=%016X Got=%016X", story_o, expected, adr_i); | |
$stop; | |
end | |
end | |
endtask | |
task assert_size; | |
input [1:0] expected; | |
begin | |
if(size_i !== expected) begin | |
$display("@E %04X SIZE_O Expected=%016X Got=%016X", story_o, expected, size_i); | |
$stop; | |
end | |
end | |
endtask | |
initial begin | |
clk_o <= 0; | |
reset_o <= 0; | |
ack_o <= 0; | |
defined_o <= 1; | |
pause_o <= 0; | |
tick(16'hFFFF); | |
// When the CPU is reset, the bus must be idle. | |
reset_o <= 1; | |
tick(16'h0000); | |
assert_size(0); | |
tick(16'h0001); | |
assert_size(0); | |
// When the CPU comes out of reset, we expect the instruction at $FFFFFFFFFFFFFF00 to be fetched. | |
// This will take a total of four clock cycles, assuming ACK_I is asserted the whole time. | |
reset_o <= 0; | |
ack_o <= 1; | |
tick(16'h0100); | |
assert_adr(64'hFFFFFFFFFFFFFF00); | |
assert_size(2); | |
tick(16'h0110); | |
assert_adr(64'hFFFFFFFFFFFFFF02); | |
assert_size(2); | |
$display("@DONE"); | |
$stop; | |
end | |
endmodule | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment